Organizer-first reference lens for the Commonality substrate. The SPA source
lives in ui/src/commonality/ and is built as
VITE_DOMAIN=commonality (npm run commonality:dev → Vite on :5174).
This directory keeps Docker/nginx, Playwright, and the product backlog.
Where the other ui/ domains are focused tool sites (Commonality, Civility,
CSM, LazyGiving, …), Commonality is organized around the cause-starter job:
- Organize a cause — retrieve, review, and select the independent, signable statements it is made of
- Enroll people — supporters (signers), volunteers, and collaborators
- Fund the work — cause boards, assurance contracts, content funding
- Use the rest as tools — Commonality thesis, Civility, CSM, Tally, etc. are supporting features, not equal top-level entry points
Directionally this is a core product surface (eventual primary entry). Known
gaps: TODO.md.
Same substrate as ui/:
- React 19 + TypeScript + Vite
- Material UI (one SPA: phone column + bottom nav, desktop workspace width + top nav; same pages)
- viem / wagmi / ConnectKit
@commonality/sdkfor chain actions and indexer queries
Launch publishes statement content via the PublishedData contract (same path
as main CreateStatementForm), not browser → Kubo API upload.
- Local deploy loads
PUBLISHED_DATA_CONTRACT_ADDRESS/VITE_PUBLISHED_DATA_CONTRACT_ADDRESSthe same way as the rest of the stack (./scripts/services.sh --startor./scripts/deploy-commonality.sh). - If the address is missing, launch fails with a clear config error.
- There is no browser
/ipfs-apiproxy. Durability mirroring is the standalonepublished-data-ipfs-mirrorworker.
Recommended while iterating on Commonality UI: keep the Docker stack for chain/indexer/IPFS/tool domains, and serve this SPA with Vite so HMR applies immediately (no image rebuild).
-
Start (or leave running) the local stack:
./scripts/services.sh --start(or at least hardhat + indexer + cause-assist + gateway).
cause-assistis a Compose service (loopback :3002). You do not need a separatenpm run cause-assist:*process for normal UI work. -
Seed
ui/.env(and optionally overlaycommonality-ui/.env) from the running Docker SPA config (contract addresses + tool domain URLs). Vite HMR on :5174 readsui/.env. IPFS/local publish mergesui/.envthencommonality-ui/.env, then root contract-address mappings:python3 scripts/seed-commonality-vite-env.py
(Needs Docker Commonality on
:8090once soconfig.jsonis available. Re-seed after a chain re-deploy. Alternatively copyVITE_*keys fromdeployments/localhost.env/ui/.env, or re-run./scripts/deploy-contracts.sh localhostwhich mirrors addresses intocommonality-ui/.env.) -
From the repo root:
npm run commonality:dev # VITE_DOMAIN=commonality in the ui package, port 5174Or from this package:
npm run dev.
Dev server: http://localhost:5174 (main ui stays on 5173).
Project lists on a cause are filtered by a Subjectiv trust graph: vouches
that “this project advances that issue” only count from accepted wallets. A
viewer who has named anyone on-chain uses their personal transitive graph. A
viewer without personal trust uses the direct trustees of the configured
VITE_DEFAULT_ALIGNMENT_TRUST_ROOT; this intentionally does not traverse the
attesters' own trust edges. The shipped local root is maintained by
alignment-trust-bootstrap, which
admits observed attesters and supports operator revocation for spam response.
That trust relationship is not an attestation of the cause itself.
./scripts/data.sh --seed (any size) records that graph for Hardhat #0–#9
via scripts/seed-local-alignment-trust.mjs. The indexer must capture
TrustRegistry:TrustSet for Commonality to see those edges. After a wipe, the
usual services.sh --start then data.sh --seed is enough.
To re-run only the trust edges: node scripts/seed-local-alignment-trust.mjs.
The cause-page disclosure identifies the starter network, and Trust settings
(gear icon) lets any connected wallet replace it by naming its own trustees.
Vite proxies /api → indexer and /api/cause-assist → Docker cause-assist on http://127.0.0.1:3002. Tool cards still open the other domains at *.localhost:8088.
Note: browser localStorage is per-origin, so causes saved on :8090 do not appear on :5174 (and vice versa). Use Vite for day-to-day UI work; use Docker (./scripts/deploy-commonality.sh → :8090) when you need the packaged nginx SPA.
Build / typecheck / test:
npm run commonality:build
npm run typecheck --workspace=commonality-ui
npm run commonality:testCommonality uses ConnectKit + wagmi.
- Browser extension wallets (MetaMask, Rabby, etc.) work via the injected connector without extra config.
- WalletConnect (QR / mobile wallets) requires a free project id from Reown Cloud. Set it before building:
# commonality-ui/.env or the shell environment used for docker build
VITE_WALLETCONNECT_PROJECT_ID=your_project_idVite bakes VITE_* into the bundle at build time, so change the id → rebuild/redeploy.
For local hardhat (chain id 31337), switch your wallet to that network after connecting (RPC http://127.0.0.1:8545), or use the built-in Hardhat #0–#9 local connectors.
After ./scripts/data.sh --seed, connect as Hardhat #0 (or any of #0–#9).
The landing page should include the seeded Local food systems cause
(/cause/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266/local-food-systems), whose
board lists the Riverside garden project and the mixed @civicbuilder content
contract, and a Christianity cause
(/cause/0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266/christianity) with the
Christian / secular-conservative mediator, three LazyGiving projects, monthly
pledges, and a mixed Common Table essay contract. Seed also publishes a
secular conservatism cause under Hardhat #9 so the bridge editor can load
a real other parent (or you can still write a stand-in).
To add only the Christianity storyline onto an already-seeded local chain:
npm run gen:seed:christianity --workspace=fake-data-generationFeatured mediator bridges come from the christian-bridge-creator Compose
service on port 3011 (./scripts/services.sh --start brings it up). The
roster still publishes without it; the mediator card then shows the service
as unavailable.
Commonality is part of the default local stack:
./scripts/services.sh --startThat publishes the Commonality SPA to local IPFS (gateway
http://commonality.localhost:8088/#/) and starts the dedicated nginx SPA on
http://localhost:8090/ with cause-assist (LLM helpers) proxied internally at /api/cause-assist/.
After start, services.sh runs a fail-fast config sync check
(./scripts/check-local-config-sync.sh / npm run local:check) so missing
PublishedData, stale ProjectFactory ABIs, or SPA address drift fail loudly
instead of at first publish/create-project. Re-run anytime:
npm run local:check
./scripts/services.sh --checkIf the check fails after a chain reset or partial redeploy:
./scripts/deploy-contracts.sh localhost # refresh deployments/localhost.env + .env + ui/.env
npm run commonality:deploy # rewrite Commonality config.json
# Republish domain UIs if LazyGiving/etc. still show old addresses:
./scripts/services.sh --start # or re-run the ui-ipfs-publisher-* servicesFocused rebuild/redeploy of only Commonality + cause-assist:
npm run commonality:deploy
npm run commonality:deploy:stopOr with compose:
docker compose build cause-assist commonality-ui
docker compose up -d cause-assist commonality-uiCommonality tool cards open the other domain SPAs at
http://{domain}.localhost:8088/#/. Admin index: http://localhost:8088/
IPFS publish for Commonality uses the shared
scripts/publish-ui-to-ipfs.mjs with UI_PACKAGE=commonality.
Included in Docker Compose (cause-assist service). ./scripts/services.sh --start
and ./scripts/deploy-commonality.sh bring it up on 127.0.0.1:3002. Docker
Commonality nginx and Vite both proxy /api/cause-assist/ to that service.
Statement suggestions and safety filter default to Grok via xAI. Optional key:
XAI_API_KEY in repo-root .env.secrets, then ./scripts/setup-env.sh.
Without a key the service still starts (template suggestions + heuristic safety).
Only run it on the host when iterating on cause-assist itself (not needed for Commonality UI work):
# stop the container first so port 3002 is free
docker compose stop cause-assist
npm run cause-assist:devSee cause-assist/README.md. Bridge-cluster wording help (brief export + one-shot verbs, no chat): docs/founder/bridge-cluster-wording-help.md.
- Landing pitch is jobs, not a movement lifecycle.
/docs(docs/end-user/commonality/index.md) is the spoken briefing: bulletin board of Kickstarters, plus delegation and retroactive funding; the rest of the docs are objections and separable jobs. Hero and/docs/the-jobs(docs/end-user/commonality/the-jobs.md) are the “do the part you’d do anyway” catalog. Do not restore Start → Grow → Deliver or “build a Movement.” - In-app docs (
/docs/*) bundledocs/end-user/commonality/,shared/, andcommonality/viaendUserDocsPlugin. Keep markdown links relative so they resolve in that viewer. Agent/protocol index:/docs/for-llms. Generated SDK/contract docs:/api-docs/sdk/and/api-docs/contracts/(Vite serves them in dev;npm run build:docsregenerates). Stdio MCP:mcp/. - Commonality is a lens, not a directory (ADR 0008).
It authors no discovery: no search, browse, ranking, featuring, or leaderboards.
A cause is reached at
/cause/:causeIdthrough a link its organizer circulates. Nothing is reviewed before it renders and nothing is listed, so there are no admission criteria; what the operated surface offers is that its numbers are correct and independently recomputable, never that the causes are good. Do not add a browse/search/"popular causes" surface — its absence is the posture, not a gap. Policy-list suppression still applies and still must reach aggregation, not just rendering. - A cause is a set of planks, not a main statement with supporters. Each
plank is published separately and carries its own CID; a cause is "live" once
any plank is on chain, and there is no launch step. The visitor's view is
/cause/…and the organizer's editor is/cause/…/edit— separate URLs, not a mode flag, so the browser's back button leaves the editor the way a reader expects. See shaping-your-cause-statements.md. - Bridges are linked, not inlined. The editor's Bridges section lists the
clusters that quote this cause as compact links to their own pages, offers
Create a bridge (
/bridge/new— human-authored, no service needed), and keeps the standalone bridge-creator instance one quiet link deeper at/cause/…/mediator. The visitor's page shows the same rows, published clusters only, with no authoring affordances. An attached mediator is one compact row on both pages — name, opt-in toggle, link out. What it proposes lives on/cause/…/mediator(BridgeDisplayBlock), never inlined into the cause. See bridge-causes.md. Commonality does not notify the quoted organizer (ADR 0011): citations are public on the cause page; optionalcontactUrlis a pointer they already use, not an inbox. - A pasted link is the parent picker. Since there is no directory to search,
the bridge editor takes the link the other organizer circulated and pulls
owner/slugout of it (parseCauseLink) — full URL, hash-routed URL, bare path, or0xowner/slug, tolerating@versionCidand trailing page segments. It refuses anything ambiguous rather than guessing at an owner. - Retrieval first; organizer approval is deterministic. Start gathers ordinary-language intent, searches published statements before asking cause-assist for new drafts, and exposes rejection/correction and manual-writing paths. Suggestions enter the same page-level review as existing drafts; exact immutable text and CIDs remain visible before publication or signing.
- Views (
CauseViewStrip+useViewCounts) are client-side set operations over the planks: a union count, and a conjunction shown as two bands (signed-all, plus signed-some-disagreed-with-none). Never render a bare intersection —noOpinionis the default, so it collapses on silence. - Alignment is per statement. The fundable-projects dashboard is inlined on
the statement page (
/statement/:cid) and, as a union of planks, on the cause page./statement/:cid/boardredirects to the statement. - Cause store (
ui/src/commonality/lib/causeStore.ts) keeps planks inlocalStorageso unpublished wording survives reloads. - On-chain actions reuse the same SDK functions the main UI uses
(
createAndSignStatement,browseStatements,believeStatement, …). - Cross-domain links resolve via the same runtime config keys as
ui(VITE_LAZYGIVING_URL,VITE_CIVILITY_URL, …) and default to the local gateway hosts.
Grok (or any agent) can drive Commonality in a real browser. Do not skip this and curl HTML — the SPA shell is empty until Chromium runs the JS.
Two layers (they are not substitutes):
| Layer | What it is | When to use |
|---|---|---|
dev-browser CLI |
Already on this machine. Playwright pages in a daemon Chromium. Skill: browser-driving. |
Always available even with no MCP. Two instances: dev-browser --browser lab-a and --browser lab-b. |
Playwright MCP (@playwright/mcp) |
MCP tools Grok can search_tool / use_tool without being told a CLI exists. |
Install so a fresh chat sees browser tools in its tool list. One server is enough — do not also add Chrome DevTools MCP. |
Protocol MCP (mcp/) |
SDK reads/writes. Not a browser. | Indexer / statements / attesters. |
Encrypted run documents live under fake-data-generation/output/test-data/.
Read fake-data-generation/output/test-data/.admin-capability yourself and
open the URL with ?key= — Adam does not treat that local key as a secret
from the agent. See fake-data-generation/README.md.
- Local stack + SPA:
./scripts/deploy-commonality.sh→ http://localhost:8090/ (or livehttps://testnet.commonality.works). The IPFS gateway copy ishttp://commonality.localhost:8088/#/. - Playwright MCP in Grok (
~/.grok/config.toml):
[mcp_servers.playwright]
command = "npx"
args = ["-y", "@playwright/mcp@latest"]
enabled = true
startup_timeout_sec = 90Register once with: grok mcp add playwright -- npx -y @playwright/mcp@latest
Then restart Grok so MCP tools load.
Protocol reads (SDK / IPFS / attesters), not the browser: mcp/README.md (npm run mcp).
- Chromium for Playwright tests (repo root):
npx playwright install chromium
Most tests should use ordinary Ethereum keypairs, not Privy emails.
- Local UI: Hardhat picker (
wallet-hardhat-0…9). Two browsers pick#0and#1. - Scripts / verifier / protocol MCP: a funded Base Sepolia key (
COMMONALITY_TESTNET_VERIFIER_PRIVATE_KEY,MCP_PRIVATE_KEY). Generate withcast wallet new(or viem); fund; never commit the key. - Two-person lab wallets:
COMMONALITY_TESTNET_LAB_A_*/_B_*fromnode scripts/generate-wallets.mjs(same as the other operational roles). Fund withnode scripts/fund-base-sepolia-wallets.mjs --only COMMONALITY_TESTNET_LAB_A_ADDRESS,COMMONALITY_TESTNET_LAB_B_ADDRESS --yes. Live UI still has no injected MetaMask in headless Chromium — signed writes use those keys via SDK /testnet.two-person-browser../scripts/verifier-testnet.sh --browserruns that check; add--mutationfor the signed write. - Live UI without Sign In: two observer browsers + those keypair writes still prove a shared on-chain world.
Privy is only the product login on deployed UIs (VITE_PRIVY_APP_ID set). One or two throwaway email users are enough to cover that path. Privy’s modal also lists MetaMask / WalletConnect (detected_wallets), so a human can import a testnet keypair into a real browser wallet and connect — that is still a normal account, just presented through Privy. Headless Chromium has no MetaMask; do not build a farm of email OTP users for ordinary journeys.
The local “injected wallet harness” (ui/e2e/fixtures/wallet.ts, window._setupTestWallet) is wagmi’s mock connector pretending a private key is MetaMask. Localhost only. Do not point the well-known Hardhat keys at Sepolia.
| Test id | Purpose |
|---|---|
wallet-connect-button |
Open Connect / show connected account |
wallet-account-menu |
Hardhat account picker (localhost only) |
wallet-hardhat-0 … wallet-hardhat-9 |
Pick Hardhat account |
wallet-disconnect |
Disconnect |
home-landing |
Root landing (first-visit pitch plus role cards) |
home-dashboard |
Role-card grid on the home landing |
home-dashboard-board |
Preview layout of the personal fundable-projects board (unused on home now) |
nav-profile |
Header icon → /profile |
personal-dashboard-page |
Full personal fundable-projects board at /dashboard |
nav-start |
Desktop/mobile nav “Start” → same (creates a new draft) |
cause-detail-page |
Cause page root (where all editing happens; brand-new drafts show “Start a cause board” coach copy here) |
issue-guidance |
Static coach copy for what an issue is |
cause-add-plank |
Add an issue |
plank-text-N |
Nth issue's editable text (drafts only) |
plank-publish-N |
Publish the Nth issue |
plank-review-button-N |
Request phrasing feedback for the Nth draft |
plank-review-N |
Feedback panel for the Nth draft |
plank-use-example-N |
Explicitly adopt the example rewording into the field |
plank-row-draft / plank-row-published |
Issue rows by state |
cause-view-strip |
Union / conjunction counts over selected statements |
plank-in-totals-N |
Include/exclude the Nth statement from those totals (view only) |
view-count-any / view-count-all / view-count-none-disagreed |
The counts themselves |
cause-keep-on-device / cause-remove-from-device |
Bookmark / remove a published cause you do not organize |
On localhost, Connect only lists Hardhat accounts (no MetaMask). Use Hardhat #0 for funded local txs.
Against the Docker SPA (hash routing, default):
npm run commonality:deploy # if not already up
npm run test:e2e --workspace=commonality-ui
# watch the browser:
npm run test:e2e:headed --workspace=commonality-uiVite dev (path routing, not hash):
COMMONALITY_UI_BASE_URL=http://localhost:5174 COMMONALITY_UI_HASH_ROUTING=0 \
npm run test:e2e --workspace=commonality-uiUse the browser. Open http://localhost:8090/, click Connect, choose Hardhat #0,
click “Start a cause board”, describe the cause, click Continue, then add and publish issues on the cause page.
This package stays thinner than ui/ (no multi-domain matrix, no Privy). Product posture:
specs/product/founder-first.md.