Sitelet https://github.com/AdamSpitz/commonality/tree/dev/commonality-ui
Skip to content

Latest commit

 

History

History

README.md

Commonality

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:

  1. Organize a cause — retrieve, review, and select the independent, signable statements it is made of
  2. Enroll people — supporters (signers), volunteers, and collaborators
  3. Fund the work — cause boards, assurance contracts, content funding
  4. 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.

Tech stack

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/sdk for chain actions and indexer queries

Statement publish = PublishedData

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_ADDRESS the same way as the rest of the stack (./scripts/services.sh --start or ./scripts/deploy-commonality.sh).
  • If the address is missing, launch fails with a clear config error.
  • There is no browser /ipfs-api proxy. Durability mirroring is the standalone published-data-ipfs-mirror worker.

Run (local dev)

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).

  1. Start (or leave running) the local stack: ./scripts/services.sh --start (or at least hardhat + indexer + cause-assist + gateway).
    cause-assist is a Compose service (loopback :3002). You do not need a separate npm run cause-assist:* process for normal UI work.

  2. Seed ui/.env (and optionally overlay commonality-ui/.env) from the running Docker SPA config (contract addresses + tool domain URLs). Vite HMR on :5174 reads ui/.env. IPFS/local publish merges ui/.env then commonality-ui/.env, then root contract-address mappings:

    python3 scripts/seed-commonality-vite-env.py

    (Needs Docker Commonality on :8090 once so config.json is available. Re-seed after a chain re-deploy. Alternatively copy VITE_* keys from deployments/localhost.env / ui/.env, or re-run ./scripts/deploy-contracts.sh localhost which mirrors addresses into commonality-ui/.env.)

  3. From the repo root:

    npm run commonality:dev
    # VITE_DOMAIN=commonality in the ui package, port 5174

    Or from this package: npm run dev.

Dev server: http://localhost:5174 (main ui stays on 5173).

Local trust network (project lists)

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:test

Wallet connection

Commonality 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_id

Vite 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-generation

Featured 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.

Local stack (core domain)

Commonality is part of the default local stack:

./scripts/services.sh --start

That 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 --check

If 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-* services

Focused rebuild/redeploy of only Commonality + cause-assist:

npm run commonality:deploy
npm run commonality:deploy:stop

Or with compose:

docker compose build cause-assist commonality-ui
docker compose up -d cause-assist commonality-ui

Tool deep-links

Commonality 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.

cause-assist (LLM helpers)

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:dev

See cause-assist/README.md. Bridge-cluster wording help (brief export + one-shot verbs, no chat): docs/founder/bridge-cluster-wording-help.md.

Design notes

  • 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/*) bundle docs/end-user/commonality/, shared/, and commonality/ via endUserDocsPlugin. 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:docs regenerates). 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/:causeId through 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; optional contactUrl is 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/slug out of it (parseCauseLink) — full URL, hash-routed URL, bare path, or 0xowner/slug, tolerating @versionCid and 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 — noOpinion is 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/board redirects to the statement.
  • Cause store (ui/src/commonality/lib/causeStore.ts) keeps planks in localStorage so 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.

Agent / browser automation

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.

Test-data admin (/admin/test-data)

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.

Prerequisites

  1. Local stack + SPA: ./scripts/deploy-commonality.sh → http://localhost:8090/ (or live https://testnet.commonality.works). The IPFS gateway copy is http://commonality.localhost:8088/#/.
  2. Playwright MCP in Grok (~/.grok/config.toml):
[mcp_servers.playwright]
command = "npx"
args = ["-y", "@playwright/mcp@latest"]
enabled = true
startup_timeout_sec = 90

Register 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).

  1. Chromium for Playwright tests (repo root):
    npx playwright install chromium

Signing in (local vs testnet)

Most tests should use ordinary Ethereum keypairs, not Privy emails.

  • Local UI: Hardhat picker (wallet-hardhat-0 … 9). Two browsers pick #0 and #1.
  • Scripts / verifier / protocol MCP: a funded Base Sepolia key (COMMONALITY_TESTNET_VERIFIER_PRIVATE_KEY, MCP_PRIVATE_KEY). Generate with cast wallet new (or viem); fund; never commit the key.
  • Two-person lab wallets: COMMONALITY_TESTNET_LAB_A_* / _B_* from node scripts/generate-wallets.mjs (same as the other operational roles). Fund with node 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 --browser runs that check; add --mutation for 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.

Stable selectors (data-testid)

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.

Smoke script (Playwright)

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-ui

Vite dev (path routing, not hash):

COMMONALITY_UI_BASE_URL=http://localhost:5174 COMMONALITY_UI_HASH_ROUTING=0 \
  npm run test:e2e --workspace=commonality-ui

Example agent prompt

Use 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.