A React + Convex app for tracking BC Societies Act compliance — registers, meetings & minutes, filings, deadlines, documents, conflicts of interest, financial statements, and PIPA privacy.
Not legal advice. Statutory references (s.11, s.20, s.40, s.42, s.56, s.36 etc.) are based on the BC Societies Act as summarised in the accompanying compliance notes — verify against the current Act and your bylaws before acting.
- Vite + React 18 + TypeScript
- Convex for live-reactive data (works against hosted Convex or a self-hosted
convex-backend) - Express auth/API sidecar for Better Auth, OpenAPI docs, API keys, webhooks, and local maintenance routes
- Better Auth for optional real login/session handling
- react-router-dom, lucide-react, date-fns, i18next, zustand
- Optional local services: RustFS object storage, n8n workflows, and BlitzBrowser-backed browser connectors
No CSS framework — a small set of hand-rolled tokens in src/theme/tokens.css mimic Twenty's design language.
The repo ships a docker-compose.yml that runs the official Convex backend/dashboard plus local support services. No need to clone anything else.
cd /Users/ahmadjalil/github/societyer
npm install
cp .env.local.example .env.local
npm run docker:up # (= docker compose up -d)This starts:
- Backend API → http://127.0.0.1:3220
- HTTP actions → http://127.0.0.1:3221
- Dashboard → http://127.0.0.1:6792
- RustFS S3 API → http://127.0.0.1:9790
- RustFS console → http://127.0.0.1:9791
- n8n workflow runtime → http://127.0.0.1:5678
- Auth/API gateway → http://127.0.0.1:8787
- API docs → http://127.0.0.1:8787/api/docs
Tail logs: npm run docker:logs. Stop everything: npm run docker:down. Wipe the database: docker compose down -v.
npm run docker:admin-key # prints the admin key — paste into .env.localYour .env.local should end up looking like:
CONVEX_SELF_HOSTED_URL="http://127.0.0.1:3220"
CONVEX_SELF_HOSTED_ADMIN_KEY="<the key the script printed>"
VITE_CONVEX_URL="http://127.0.0.1:3220"
If you are using the RustFS service under OrbStack for document version uploads, create the local bucket and point Convex at the service domain:
npm run rustfs:setupIf the helper script can't find the binary in your image version, open the dashboard (http://127.0.0.1:6792) — it can generate one too.
npx convex dev # terminal 1 — pushes ./convex to localhost:3210
npm run dev # terminal 2 — Vite at http://localhost:5173docker:up already starts the auth/API gateway container. If you stop that container and want to run the gateway directly on the host instead, use npm run dev:full.
- Click Seed demo society in the in-app demo banner, or
⌘K→ "Seed demo society" in the command palette, ornpm run convex:seedfrom the terminal
In-app seed/reset calls go through the local API gateway so the Convex maintenance token never reaches the browser. Wipe with the settings page or npm run convex:reset.
npm install
npx convex dev # will prompt you to log in; creates a free cloud deployment
npm run devUse this when the Kubernetes stack is already running and you only want a local Vite frontend pointed at the shared K8s Convex data:
npm install
npm run dev:k8sThe command starts Vite at http://127.0.0.1:5173 with:
VITE_CONVEX_URL=http://societyer.k8s.home:3220
VITE_AUTH_MODE=none
Override the backend URL if DNS is unavailable or you are working against a different cluster:
VITE_CONVEX_URL=http://192.168.1.50:3220 npm run dev:k8sThis is for frontend work against existing deployed data. Convex function,
schema, or server/API changes still need to be deployed to the target K8s
runtime with a valid CONVEX_SELF_HOSTED_ADMIN_KEY.
Deploy local Convex schema/functions to the K8s runtime with:
npm run convex:deploy:k8sThe helper uses KUBECONFIG if set, otherwise it falls back to
/Users/ahmadjalil/github/personalprox/kubeconfig.yml, generates a short-lived
admin key inside the societyer-convex pod, and runs npx convex dev --once
against http://societyer.k8s.home:3220.
- The in-app banner appears whenever demo mode is on (default).
- Toggle in Settings → Demo mode.
- Append
?demo=0to any URL to suppress it,?demo=1to force it on. - The seeded society is Riverside Community Society — a fictional BC charity with 7 directors, 15 members, two past meetings (a 2025 AGM and a Q4 board meeting), minutes with motions/decisions/action items, filed and upcoming filings (annual report, T3010, T4, GST/HST, bylaw amendment, change of directors), recurring deadlines, a disclosed conflict of interest, FY2024-25 financials with remuneration disclosure, and a document repository with retention flags.
/opens the backend-backed workspace app. In auth-enabled deployments it gates into login; in no-auth deployments it opens the dashboard directly./demoboots the same React app shell and/app/...routes in a browser-only in-memory fixture mode. It does not require Convex, auth, localStorage app state, IndexedDB, SQLite, or seeded backend data.- Inside demo mode, app links are automatically scoped under
/demo, so/app/filingsbecomes/demo/app/filings. /appremains supported as a workspace route alias for existing links./public/:slugand other operational routes still expect the live app stack.
Societyer now supports two identity modes:
VITE_AUTH_MODE=none/AUTH_MODE=none— keep the current local/demo workflow. The user picker remains available and no auth server is required.VITE_AUTH_MODE=better-auth/AUTH_MODE=better-auth— enable Better Auth for real login/session handling. Start the auth sidecar alongside Vite:
npm run dev:fullRunning the auth sidecar directly requires Node 22.5+ because Better Auth uses node:sqlite. The Docker auth-server service already uses Node 22.
Settings → Modules can enable or hide optional surfaces by society. Current module groups include:
- Engagement: Communications, volunteer management, and grant management.
- Governance: voting and resolutions, elections, proxies, auditors, director attestations, and court orders.
- Compliance: filing pre-fill, records retention/inspection, PIPA training, insurance, access custody, and public transparency.
- Finance: reconciliation, donation receipts, membership billing, employee records, and grant finance workflows.
- Integrations: Paperless-ngx, browser connectors, and workflow automation.
Where to set these: every secret below is read by Convex functions, which see the Convex deployment's environment — not the repo-root
.env.local(that file is only read by Vite and the auth/API server). Set them on the deployment, otherwise the integration stays in demo mode even though the value is in.env.local:npx convex env set RESEND_API_KEY re_xxx # hosted # self-hosted: add --url <convex-url> --admin-key <key>, or use the self-hosted dashboard
- Resend: set
RESEND_API_KEYplusRESEND_FROM_EMAILto turn digest emails and communications into real outbound email. - Twilio: set
TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN, and eitherTWILIO_FROM_NUMBERorTWILIO_MESSAGING_SERVICE_SIDfor SMS delivery and callback tracking. - Stripe: set
STRIPE_SECRET_KEYandSTRIPE_WEBHOOK_SECRETto enable real hosted checkout. Point Stripe webhooks at Convex HTTP route/stripe/webhook. - Wave: set
WAVE_ACCESS_TOKENandWAVE_BUSINESS_IDto switch the accounting sync from demo data to live GraphQL fetches.WAVE_CLIENT_IDandWAVE_CLIENT_SECRETare reserved for OAuth setup and are reported only as present/missing in diagnostics;WAVE_GRAPHQL_ENDPOINTis an optional override for the default Wave GraphQL endpoint. The Financials page includes a Wave health check that reports only present/missing env status plus redacted provider diagnostics. See docs/wave-accounting-api-findings.md for the current read/write limits, including Wave's lack of a public bank/credit-card ledger transaction read API. - LedgerSMB:
LEDGERSMB_BASE_URLenables the provider adapter boundary; optionalLEDGERSMB_DATABASE,LEDGERSMB_USERNAME/LEDGERSMB_PASSWORD, orLEDGERSMB_API_KEYconfigure company selection and auth. The current scaffolding normalizes accounts and journal entries from/api/accountsand/api/journal-entrieswithout changing the financial UI. - Paperless-ngx: set
PAPERLESS_NGX_URLandPAPERLESS_NGX_TOKEN, then enable the Paperless-ngx module at/app/paperless. Documents can be sent to Paperless from/app/documents; Societyer creates contextual tags such as document category, filing kind, grant report, PIPA training, election evidence, and volunteer screening. - RustFS / S3-compatible storage: set
RUSTFS_ENDPOINT,RUSTFS_BUCKET,RUSTFS_ACCESS_KEY, andRUSTFS_SECRET_KEYfor storage-backed document versions. The bundled Docker stack includes RustFS for local development. - OpenAI / Anthropic / Whisper-compatible transcription: set
OPENAI_API_KEY,ANTHROPIC_API_KEY,WHISPER_API_KEY, orWHISPER_ENDPOINTto move meeting transcription and minute drafting out of deterministic demo mode. - n8n: set the
N8N_*andSOCIETYER_WORKFLOW_*values to run workflow bridge recipes, including the committed UNBC and Over the Edge examples in integrations/n8n. - Browser connectors: run
npm run docker:connectorsand setCONNECTOR_RUNNER_SECRETto enable saved browser sessions, connector actions, and exports through BlitzBrowser. - Access custody vault: set
SECRET_VAULT_ENCRYPTION_KEYbefore storing client credentials in production./app/access-custodyencrypts stored values, keeps them hidden by default, and logs explicit reveals to the activity trail. - Filing evidence: BC still has no public Societies Online filing API. This build improves the manual path by storing submission method, confirmation number, fee paid, and linked receipt/evidence documents when a filing is marked filed.
To run a smaller instance — or to guarantee an operator that no files are ever
stored in Convex — set SOCIETYER_DISABLE_NATIVE_FILE_STORAGE=1 on the Convex
deployment (npx convex env set SOCIETYER_DISABLE_NATIVE_FILE_STORAGE 1) and
VITE_DISABLE_NATIVE_FILE_STORAGE=1 for the client. When enabled:
- Every native upload path throws server-side — document upload, document versions, inventory item images, meeting audio/video, and the Paperless "pull into Convex" copy — so users can't smuggle a file in even via direct API calls. The client also hides the upload affordances.
- External document connectors stay usable as read-only sources: Paperless documents can be listed, linked, and opened at the source; they just aren't cached into Convex. You have a document source without a native document store.
- The only exception is the society logo / letterhead (branding, not document content), which still uploads via a dedicated path.
Auth mode uses a small SQLite auth database configured by AUTH_DB_PATH, and maps signed-in identities into the existing Convex users / members records.
Important — identity is client-asserted today. The product UI talks directly to Convex, Convex functions do not call
ctx.auth, and role checks trust anactingUserIdpassed by the client.better-authgates the SPA login, not the Convex endpoint. This is intentional for local-first / single-tenant use but is not safe for an untrusted multi-tenant deployment. See docs/security-and-auth-posture.md for the full model and the multi-tenant hardening checklist.
The auth sidecar also mounts Societyer's REST API gateway:
- OpenAPI JSON:
/api/openapi.json - Swagger UI:
/api/docs - Versioned API:
/api/v1
The gateway exposes society, people, governance, filings, documents, finance, volunteer, grant, workflow, plugin, webhook, API-client, and API-token resources backed by Convex. Local development can use trusted localhost requests; production should configure SOCIETYER_API_PLATFORM_TOKEN, SOCIETYER_MAINTENANCE_TOKEN, API_TOKEN_PEPPER, and API_SECRET_ENCRYPTION_KEY.
This repo now includes a GitHub Pages workflow at .github/workflows/deploy-pages.yml and a custom-domain file at public/CNAME.
For the public site:
npm run build:pagesbuilds the app with stableassets/index.jsandassets/index.cssfilenames, writesdist/404.htmlfor SPA fallback, and writesdist/demo/index.htmlso/demoresolves directly./demouses src/lib/staticConvex.ts to satisfy the real frontend's Convex queries with in-memory fixture data, and src/lib/staticRuntime.ts keeps demo-only app state out of persistent browser storage.- The configured custom domain is
society.ahmadjalil.com. - In the GitHub repository settings, Pages should use GitHub Actions as the source.
| Area | Page | Notes |
|---|---|---|
| Workspace | / |
Backend-backed app entry point; opens the dashboard after auth/no-auth resolution |
| Public | /demo Browser-only demo |
The real app frontend using an in-memory Convex-compatible fixture client |
| Public | /public/:slug |
Transparency center with optional volunteer and grant intake pages |
| Workspace | /app, /app/society, /app/organization-details, /app/org-history |
Compliance dashboard, legal profile, organization identifiers/addresses/registrations, and source-backed history |
| People | /app/members, /app/directors, /app/role-holders, /app/committees |
Member and director registers, role holders, committees, goals, tasks, and committee-linked work |
| Work | /app/tasks, /app/deadlines, /app/commitments, /app/documents, /app/library |
Work tracking, recurring deadlines, obligation extraction, document workbench, document versions, and board/library packets |
| Meetings & votes | /app/meetings, /app/agendas, /app/minutes, /app/elections, /app/proposals, /app/proxies |
Meeting packages, AGM workflow, motions, minutes, member proposals, anonymous ballots, written resolutions, and proxies |
| Governance records | /app/conflicts, /app/attestations, /app/auditors, /app/governance-registers, /app/minute-book |
Conflict disclosures, annual director attestations, auditors, evidence registers, minute-book assembly, and bylaw-driven rules |
| Compliance | /app/filings, /app/filings/prefill, /app/privacy, /app/retention, /app/inspections, /app/access-custody |
Registry/CRA filing tracker, filing pre-fill, PIPA program, records inspection, retention, insurance, and credential custody |
| Finance | /app/financials, /app/finance-imports, /app/treasurer, /app/reconciliation, /app/receipts, /app/membership |
Financial years, Wave cache views, treasurer workflows, imports, reconciliation, donation receipts, and Stripe-backed billing |
| Engagement | /app/communications, /app/volunteers, /app/grants, /app/transparency |
Campaigns, AGM notice proofs, volunteer screening/intake, grant pipeline/intake, and public transparency publishing |
| Workflows | /app/workflows, /app/workflow-runs, /app/workflow-packages, /app/browser-connectors, /app/template-engine |
Workflow canvas, n8n bridge runs, generated document packages, browser-backed connectors, and template field mapping |
| Administration | /app/users, /app/custom-fields, /app/imports, /app/paperless, /app/audit, /app/exports, /app/settings |
Users/roles, custom fields, import sessions, Paperless sync, audit log, redacted data export, modules, auth mode, and runtime settings |
Press ⌘K (or Ctrl+K) anywhere for the command palette.
convex/minutes.ts exports generateDraft as a Convex action. In demo mode it returns deterministic draft minutes; with provider env vars it can call the live LLM/transcription adapters:
OPENAI_API_KEY,OPENAI_MODEL, orOPENAI_MINUTES_MODELfor OpenAI chat completions.ANTHROPIC_API_KEY,ANTHROPIC_MODEL, orANTHROPIC_MINUTES_MODELfor Anthropic messages.WHISPER_API_KEY,WHISPER_ENDPOINT, orOPENAI_API_KEYfor audio transcription.
The meeting detail UI already uploads audio, tracks transcription jobs, stores the transcript, and pipes text into generateDraft.
npm run build— Convex typecheck, TypeScript build, and Vite production build.npm run build:pages— GitHub Pages build with stable asset names and/demofallback files.npm run dev:k8s— run a local Vite frontend against the Kubernetes Convex backend atsocietyer.k8s.home:3220.npm run lint/npm run lint:convex— ESLint for the app or Convex functions.npm run test:smoke— Playwright smoke tests.npm run test:api-contract— API gateway contract check.npm run test:dashboard-compliance— dashboard compliance rule check.npm run test:org-details— organization detail smoke check.npm run test:pdf-ingestion— PDF table normalization check.npm run test:exports/npm run test:exports:db— export coverage and database export validation.npm run docker:connectors— start the optional connector-runner, BlitzBrowser, and connector dashboard profile.npm run connector-runner:typecheck— typecheck the browser connector service.npm run templates:json/npm run templates:exports— regenerate starter policy template JSON and rendered documents.
- BC society workspace records: society profile, members, directors, meetings, minutes, conflicts, filings, deadlines, documents, bylaws, privacy, financials, elections, grants, volunteers, transparency, and module settings.
- Public/static demo and backend-backed app modes:
/demoruns without Convex or auth;/appruns against hosted or self-hosted Convex. - Identity modes:
nonekeeps the local/demo user picker;better-authenables real login/session handling through the auth sidecar. - Multi-society data model and workspace selection: the app can list society records and persists the selected society locally.
- Document files and versions: Convex storage uploads plus RustFS-backed document version uploads are wired into the document workflows.
- Filing support: Societies Online and CRA pre-fill payloads can be reviewed, copied, and exported for manual filing; filed records store submission method, confirmation number, fee, and evidence.
- Audit surfaces: the audit log page, election audit trail, exports with redaction, and access-custody reveal logging are available.
- Live integrations behind environment flags: Resend email, Twilio SMS, Stripe checkout/webhooks, Wave accounting sync, Paperless-ngx document sync/import, RustFS storage, LLM/transcription adapters, n8n workflows, and browser connectors.
- Better Auth production rollout: login/session handling is implemented, but production deployments still need env hardening, domain/cookie review, and operational migration testing.
- Payment and email operations: Stripe and Resend paths exist, but should be tested against real provider accounts before depending on them for a society's live workflows.
- SMS operations: Twilio delivery and callbacks exist, but phone-number registration and consent workflows need provider-specific production review.
- Wave accounting: account, balance, vendor, product, invoice, and provider diagnostics are supported within Wave's public API limits; bank and credit-card ledger reads still require CSV/import or another data source.
- Paperless-ngx automation: document sync, tags, and source-document import flows exist and should be validated against each organization's Paperless taxonomy before broad use.
- Multi-society operations: multiple records and selection work, but account-level permissions, membership scoping, and admin controls need more production review.
- Workflow and browser automation: n8n bridge recipes and BlitzBrowser connector sessions are available for local/operator-driven workflows, but should be treated as controlled automation rather than unattended filing authority.
- Direct Societies Online submission or browser-assisted filing beyond pre-fill review, subject to BC Registry access constraints.
- More robust speaker diarization and transcript review tooling for meeting minutes.
- Slack or other non-email deadline reminders.
- Deeper CRA T3010 / T2 line-item schedules and validation.
- Production-grade role/permission boundaries across societies, modules, and sensitive records.
- Metadata-only document tracking as the expected document workflow. New document work should use storage-backed uploads, document versions, and Paperless sync where configured.
- Treating authentication and multi-society support as out of v1 scope. Both now exist in working form, with production hardening tracked under beta/planned.
- Describing Societies Online filing as unavailable wholesale. Direct API filing is still unavailable, but pre-fill review/export and evidence capture are current features.