Build local edition web console
## Original requirement (verbatim, epic body)
> Build new deployment type for Jaah: local. Do a web research and copy the model of OpenClaw, N8N and other similar products:
> - Open source in a public repo (for not private), mirrored some of the components from main repo
> - A package of node + CP that user runs on his machine, no multiple machines, single user, no advanced functionality, no networking, no nodes
> - Should be a very easy start - one line install and/or build from repo and/or docker compose
> - Limited number of accounts
>
> Analyze and propose options
Settled upstream (do not reopen): PRD (Task 10080) decisions D1–D16 + assumptions S1–S9; Architecture (Task 10081) decisions D17–D29 — the **1+3 hybrid**: one codebase with a build-time edition (three literal constants rewritten only by the mirror build), an allowlist manifest + tree builder + per-PR mirror-build gate, caps enforced in the CP and the node, public repo `jaah-ai/jaah-local` (fair-code, read-only mirror). Binding numbers: 1 user, ≤3 AI provider accounts in total, ≤10 concurrent agent sessions (over-cap work queues), no telemetry, loopback only. Business goal (D4): a funnel to Jaah Cloud via upsell links only (D8).
## Scope of this child
**C6. Web local edition (Arch §2.8).** Owns AC-12 (UI), AC-14 (UI), AC-21 (UI), AC-23. Builds against C3's and C4's pinned contracts (below) and mocks them until they land — no dependency edge.
## Approach (from the settled architecture — realize it, do not redesign)
- **Routes.** Move cloud-only routes and nav entries into `apps/web/src/routesCloud.tsx` exporting `cloudRoutes` and `cloudNavItems`; `App.tsx` and `Sidebar.tsx` load it via `import.meta.glob('./routesCloud.tsx', { eager: true })` (yields `{}` when absent, so the public build compiles with no stub). Settings tabs for removed surfaces follow the same file. The manifest already excludes it; widen `include` for `apps/web`.
- **Local-only screens (`IS_LOCAL`):**
- first-run screen consuming `GET/POST /api/local/setup` (`{"needs_setup": bool}`; `POST {"email","password"}` ≥12 chars → 201 + cookie; 409 if a user exists);
- no sign-up, invite or Users pages;
- forge connect shows **token paste only** for GitHub, GitLab, Bitbucket and Azure DevOps (D22);
- a **"This machine"** panel in Settings showing `node.online` and `last_seen` from `GET /api/local/limits` (`{"accounts":{"used","cap"},"agents":{"running","cap","queued"},"node":{"online","last_seen"},"upgrade_url"}`) — the only node UI;
- the Help button links to the docs (Help assistant removed, D28).
- **Cap refusal rendering:** the 409 payload `{"detail","kind","limit","upgrade_url"}` renders inline as one line with the link — no blocking modal.
- **Upsell lines — the complete list (AC-23), one line each with the `upgrade_url` link, nowhere else:** (1) the Accounts add-refusal (cap reached); (2) the Tasks board while `agents.queued > 0`; (3) the "This machine" panel: "Run agents on more machines with Jaah Cloud"; (4) the user menu, in place of Users and invites: "Invite your team on Jaah Cloud".
- Follow `docs/standards/ui-style-guide.md` and `docs/standards/ui-copy-standards.md`; check phone widths.
- **Gate:** add `vite build` of the built tree to `local-edition-check.yml`.
Full documents: the epic's settled `prd` and `architecture` artifacts (Tasks 10080/10081).
Credentials & reach:
- AC-3, AC-17 grep, AC-8 migrations, C1–C6 unit and gate checks — nothing beyond the worktree (plus GitHub Actions for `local-edition-check.yml`) — **verified**: the workflow runs in this repo's existing CI
Out of scope:
- Do NOT implement the API endpoints (C3/C4 own them — mock to the pinned contract), the router split (C2), node work (C5) or distribution (C7a).
- Do NOT add a new alembic revision.
- Do NOT add OAuth Connect for forges in local (D22). Do NOT add upsells beyond the four listed.
- Cloud nav, routes and settings must render identically when `IS_LOCAL` is False (add a test pinning the cloud nav set).
PRD §7 (verbatim) — the local edition, and the work to build it, MUST NOT:
- Change the behaviour, UI or data model of the cloud deployments (dev, `dev.jaah.ai`; prod, `app.jaah.ai`). Any edition switch must default to today's behaviour there.
- Ship, in the local edition or in the public repo: Stripe or billing; SES or system mail; Slack or Telegram; headscale or tailnet; cloud node launch (`apps/api/app/cloud_node_launch.py`); node groups (`apps/api/app/node_group_reconcile.py`); Multi-Node Hosts (`apps/api/app/vnodes.py`); NodeLink (`apps/api/app/nodelinks.py`); MicroVM (`docs/policies/microvm-nodes.md`); Jarvis (`apps/site-widget/`); the support knowledge base (`apps/api/app/support_kb/`); project memory (`apps/api/app/project_memory/`); SSO, SCIM or multi-org tenancy; the mobile app (`apps/mobile/`); GitHub Actions self-hosted runners (`infra/host/gha-runners/`); `infra/jaah/` (Terraform) and `infra/host/`.
- Require pgvector, Redis, S3/MinIO, or any cloud account.
- Phone home: no telemetry, no version check, no licence or activation server.
- Accept community PRs into the public repo, or require a CLA.
- Offer data export from local to Jaah Cloud, or attaching the local node to a cloud org.
- Rewrite the control plane for SQLite, or install the control plane natively without Docker.
- Expose any port beyond loopback, or support more than one machine.
- Make the caps (3 accounts, 10 concurrent sessions) configurable by any setting, env var, flag or UI.
Done means:
- **AC-12 (R4), UI:** "there is no sign-up page, no invite UI, and no Users management page." — verify: component/e2e test in local mode finds none of them; the first-run screen appears when `needs_setup` is true and signs the user in.
- **AC-14 (R6), UI:** "the console has no Nodes list, no 'Add node', no node groups, no Multi-Node Hosts, and no NodeLink screens." — verify: test over the local nav + settings; the "This machine" panel shows online/last-seen from the limits payload.
- **AC-21 (R18), UI:** "walk every top-level navigation entry and every Settings tab of the local console. Observe none of the following: billing, plans or Stripe; orgs or tenant switching; users and invites; SSO or SCIM; nodes, node groups, Multi-Node Hosts, NodeLink, MicroVM; Jarvis (site widget, Support → Website chats); the Help assistant knowledge base; project memory; Slack and Telegram; headscale or tailnet; Audit SIEM export; data residency. Quality: no empty tab, dead link, or 'coming soon' placeholder is left where a removed surface was. Where a removed feature is a natural next step, an upsell link (AC-23) stands in its place." — verify: an automated walk of every nav entry and Settings tab in local mode asserts none of those surfaces, no empty tab, no link to a removed route, no "coming soon"; screenshots at desktop and phone width attached to the PR, each page laid out cleanly (no orphan section headers, no gaps where a tab was).
- **AC-23 (R20):** "hitting the account cap (AC-15), hitting the concurrency cap while tasks queue (AC-16), and the places of removed cloud features each show one short line with a working link to Jaah Cloud sign-up… no modal blocks work, and no upsell appears on pages where no limit or removed feature is involved. Copy follows `docs/standards/ui-copy-standards.md`." — verify: tests for each of the four lines (present when its condition holds, absent otherwise) and a grep that `upgrade_url`/`CLOUD_SIGNUP_URL` links render nowhere else.
- Forge connect in local shows token paste only for all four forges — test.
- Cloud nav parity test green; `vite build` of the built tree green in `local-edition-check.yml`; `./dc check quick` green.
Outcome
Local edition console built: cloud routes/nav/Settings panels split into glob-loaded files, local first-run, This machine, token-only forges, 4 upsells; tree build in CI green; quick gate + 12078 web tests green. PR #6944 queued on lane. Follow-up draft 10138. Visual check done at desktop and phone: clean. Memory: used mem-25288, mem-18304; proposed none.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)