The user-visible payoff of the whole delivery workstream. - (app)/app: four tabs — pipeline (runs + the eight steps + a live console), releases (with demo rollback), production (target summary, confirmed deploy, history), Git. The header answers "is it live": demo URL, live release, health state. - Live console: batches onto an animation frame, stops following the tail the moment the user scrolls away from the bottom, strips ANSI colour codes and carriage-return redraws. A console that yanks the view back is worse than none. - Production panel: the target is shown as the operator recognises their server plus has_secret/ثبت شده. No field anywhere could render a credential. - contracts: the v2 delivery types and seven new WsEventType members. - /admin/sandbox (owner): per-role container counts, running containers, per-org limits, image mapping, stop with the id typed back (audited). A provider without the capability reports an empty list rather than an error. - /admin/domains (owner): every route with its owning app, attach/detach (host typed back), idempotent re-sync. - /projects removed entirely, with a permanent redirect to /app. Tests: 502 API (+3), 78 web (+11); ruff, mypy, tsc and lint clean. Report: docs/plan/reports/agent-3-phase-6.md
6.4 KiB
Agent 3 — Phase 6 report: the Application page
Date: 2026-10-14 · Branch: dev
What was done
6.4 lib/api-delivery.ts — the typed client
~15 functions covering CI/CD runs, releases & runtime, production deploy/rollback
and the owner domain console. Transport stays in the locked lib/api.ts; this module
only composes paths.
packages/contracts gained the whole v2 delivery surface — PipelineRun,
PipelineRunDetail, Release, AppRuntime, ProductionTarget,
ProductionDeploy, ProductionOverview, DomainRoute, SuggestedRoute — plus the
seven new WsEventType members (pipeline.queued/step/log/status,
release.live, deploy.status) and their live payload shapes.
6.1 (app)/app/page.tsx
Four tabs, in the order the questions are actually asked:
| Tab | Shows |
|---|---|
| خط لولهٔ انتشار | the runs, and (once one is opened) its eight steps plus a live log |
| نسخهها | the release history, with demo rollback (admin+) |
| production | the target summary, deploy/rollback with confirmation, history |
| Git | the dev head and nothing else — no branch, port or command controls |
The header answers "is it live": app name, status badge, demo URL, live release sha and its timestamp, health state.
_components/pipeline-console.tsx — the live build console. Three details make it
usable rather than merely live:
- batching — lines are coalesced onto an animation frame, so a burst does not thrash the DOM;
- auto-scroll that yields — following the tail stops the moment the user scrolls away from the bottom (the classic cause of a console that yanks the view back);
- ANSI stripped — a build log is full of colour codes; rendered raw they are
unreadable noise.
stripAnsihandles CSI sequences and carriage-return redraws.
_components/production-panel.tsx — the target is shown as the operator
recognises their own server (host, port, path) and as ثبت شده/ثبت نشده for the
credential. There is no field anywhere that could render one. A user who is not an
org admin sees the history and no buttons at all; a missing target renders an empty
state rather than a disabled button.
6.3/6.4 /admin/sandbox and /admin/domains
GET /api/admin/sandbox/containers + POST .../{id}/stop (owner-only, container id
typed back, audited) live on a separate admin_router in sandbox.py — the tenant
router in that module is org-scoped and the owner console must never be. /admin/sandbox
shows per-role container counts, the running containers, the per-org resource limits and
the base-image mapping; /admin/domains shows every route with its owning app, attracts
and detaches hosts (host typed back), and re-syncs.
Both consoles degrade honestly: a provider without list_managed_containers yields an
empty list rather than an error, because a CLI outage must not look like "no
containers".
6.2 /projects is gone
Removed entirely; next.config.mjs now has a permanent /projects* → /app redirect, so
a bookmark or a mid-flow history entry lands somewhere useful instead of on a 404. The
member-facing preview-domain endpoints were already made owner-only in Phase 4.
Files changed
| File | Change |
|---|---|
apps/web/src/lib/api-delivery.ts |
the typed client |
apps/web/src/app/(app)/app/page.tsx |
the Application page |
apps/web/src/app/(app)/app/_components/pipeline-console.tsx |
new |
apps/web/src/app/(app)/app/_components/production-panel.tsx |
new |
apps/web/src/app/(app)/admin/domains/page.tsx |
the owner domain console |
apps/web/src/app/(app)/admin/sandbox/page.tsx |
the owner sandbox console |
apps/web/next.config.mjs |
/projects* → /app redirect |
apps/web/src/app/(app)/projects/** |
deleted |
packages/contracts/src/index.ts |
v2 delivery types + event types |
apps/api/app/interfaces/http/routers/sandbox.py |
admin_router with 2 owner routes |
apps/api/app/interfaces/realtime/rpc_app.py |
mounts it |
tests/test_docker_sandbox.py |
+3 sandbox-console tests |
apps/web/src/test/delivery-app.test.tsx |
new — 11 tests |
Test output
apps/api: 502 passed, 1 skipped (was 499; +3)
ruff check . -> All checks passed!
mypy app -> Success: no issues found in 136 source files
apps/web: 78 passed (was 67; +11)
tsc --noEmit -> clean
next lint -> No ESLint warnings or errors
Deviations from the TODO
- The sandbox console's backend is two routes in
sandbox.pyrather than its own router file. The plan assignsprojects.py,sandbox.py,delivery.pyandadmin_domains.pyto Agent 3; adding a sixth router for two endpoints would have meant putting them in a file the ownership table does not name. A second router object insandbox.pykeeps the ownership right and the mount explicit. - The Application page defaults to the org's single project. One org has one
application (D1), so there is no picker to build — and no app profile endpoint yet,
because that is Agent 1's Phase 2. The Git tab therefore shows the
devhead rather than the app-type badges the TODO's header describes; those arrive with the profile. - The deploy button appears for any signed-in member. The server is the
authorization boundary (the API tests assert a non-admin gets 403), and the web only
has the platform role, not the org role. Rendering optimistically and letting the
server refuse is the honest option; a fake local gate would be worse. The reason is
in a comment in
production-panel.tsx.
Open questions / CHANGE REQUESTs
- CHANGE REQUEST (for integration): the web needs the caller's org role to gate
the production buttons client-side.
MeResponse.organizationsalready carriesOrganizationWithRole, so it is a small addition toSessionValue— noted so it is added deliberately rather than duplicated as a guess.
Known limitations
/admin/sandboxshows and stops containers; it does not restart them. There is no "start" because a stopped workspace container is recreated on demand by the next session, and a restart button would imply a guarantee the lifecycle does not make./admin/domains's attach form picks the project from the routes already registered rather than from a picker. Assigning a host to a specific app of a multi-project owner needs the app list, which is Agent 1's work.