From 98e631c2f2acd32f10a7d678997cc35524f005a9 Mon Sep 17 00:00:00 2001 From: 4ndreello <4ndreello@users.noreply.github.com> Date: Tue, 22 Sep 2026 21:55:15 -0300 Subject: [PATCH 1/3] docs(web): Add web console spec, design and tasks Define phased requirements, architecture and implementation tasks for the local web console while preserving current terminal and batch contracts. Co-Authored-By: Codex --- .specs/features/web-console/design.md | 259 ++++++++++++++++ .specs/features/web-console/spec.md | 261 ++++++++++++++++ .specs/features/web-console/tasks.md | 418 ++++++++++++++++++++++++++ 3 files changed, 938 insertions(+) create mode 100644 .specs/features/web-console/design.md create mode 100644 .specs/features/web-console/spec.md create mode 100644 .specs/features/web-console/tasks.md diff --git a/.specs/features/web-console/design.md b/.specs/features/web-console/design.md new file mode 100644 index 0000000..383302f --- /dev/null +++ b/.specs/features/web-console/design.md @@ -0,0 +1,259 @@ +# Web console design + +**Spec**: .specs/features/web-console/spec.md + +**Status**: Draft + +--- + +## Architecture Overview + +Each CLI entry starts the same in-process HTTP server with a command-specific root path and route table. The shared router checks Host before dispatch. Action routes also check the per-start token and Origin. The server closes when its CLI command receives SIGINT or SIGTERM. + +The UI command serves the home page and all registered pages. Review keeps its current root behavior when invoked as codedeck review. Setup and Usage open their page routes directly. Every page stays a self-contained HTML string. + +~~~mermaid +flowchart LR + C[CLI commands] --> S[Shared server and route table] + S --> H[Host check] + H --> O[Origin and token check on POST] + O --> R[Route handler] + R --> RP[Review page and API] + R --> SP[Setup page and API] + R --> UP[Usage page and API] + SP --> P[Pure setup planner] + P --> W[Config writer] + UP --> Q[fetchUsageQuery] + Q --> I[Daemon IPC] + Q --> D[Read-only SQLite fallback] +~~~ + +The route table receives handlers from the CLI commands. This keeps the router independent of setup, usage, and review internals. The server remains in the CLI process and does not start or delegate work to the daemon. + +## Research Notes + +The repository already uses Node's built-in HTTP server in review.ts and declares Node 24 or newer in package.json. The official Node 24 HTTP docs cover createServer, listen, request headers, and server close. The official crypto docs cover randomBytes for the per-start token. No HTTP or frontend dependency is needed. + +- Node.js v24 HTTP documentation: https://nodejs.org/download/release/latest-v24.x/docs/api/http.html +- Node.js v24 crypto documentation: https://nodejs.org/download/release/latest-v24.x/docs/api/crypto.html +- Context7 MCP was not available in this session. +- .specs/STATE.md is absent, so there are no active project decision entries to apply. + +## Code Reuse Analysis + +### Existing components to leverage + +| Component | Location | How to use | +| --- | --- | --- | +| Review port parsing, browser opening, and request behavior | src/cli/commands/review.ts:7-135 | Move the shared server concerns into src/web/server.ts and keep review route behavior in the review handler. Preserve its default port and CLI flags. | +| Review HTML | src/web/review-page.ts:8-662 | Serve the existing string without adding assets or changing page behavior. | +| Usage query and fallback | src/cli/commands/usage.ts:37-77 | Inject fetchUsageQuery into the web usage handler so the page uses daemon IPC and the existing read-only SQLite fallback. | +| Usage request and result types | src/daemon/protocol.ts:160-211 | Use UsageQueryParams and UsageQueryResult. Add the optional byOrigin rendering only after feat/orchestrator-usage merges. | +| Setup wizard and selection rules | src/cli/commands/setup.ts:580-801 | Keep the existing wizard, but have it pass completed selections through the shared planner before saving. | +| Setup profile and config helpers | src/config/config.ts:48-76, :103-137, :237-257, :543-548 | Reuse RunAgentConfig, profile snapshot helpers, SetupConfigRead, SetupConfigStore, and saveConfig. | +| Setup proposal and diff behavior | src/cli/commands/setup.ts:1000-1036, :1229-1245, :1257-1332 | Move the pure proposal, diff, and binding validation logic into the shared planner while preserving SetupEnvelope fields and status values. | +| Model discovery | src/core/models.ts:151-190 | Use the cached catalog for GET and invoke getCachedOrDiscoverModels with refresh=true from the protected refresh route. | +| Existing review, setup, and usage tests | tests/review.test.ts, tests/review-command.test.ts, tests/setup-wizard.test.ts, tests/setup-cli-contract.test.ts, tests/usage*.test.ts | Preserve current assertions and add focused tests beside the new server, handlers, pages, and command wiring. | + +### Integration points + +| System | Integration method | +| --- | --- | +| CLI command registration | Add codedeck ui in src/cli/index.ts; keep web startup in the existing review, setup, and usage commands. | +| Review data | Keep GET /api/review read-only and delegate to the current local review loader. | +| Setup config | The handler reads through the existing config store, calls the pure planner, then saves only after validation passes. | +| Setup model catalog | GET returns cached status and models. A protected POST starts or joins discovery and shows the page's discovering state while it is pending. | +| Usage analytics | The handler translates URL filters to UsageQueryParams and calls the same fetchUsageQuery used by the CLI. | +| Daemon | No changes. Usage continues to use the current IPC query and SQLite fallback from the CLI process. | + +## Components + +### Shared web server + +- **Purpose**: Start a loopback-only Node HTTP server, dispatch a route table, print the URL, and close with the command. +- **Location**: src/web/server.ts +- **Interfaces**: + - createWebServer(options): creates the route listener with a command-specific root page. + - startWebServer(options): listens on 127.0.0.1, opens the requested URL unless --no-open is set, and returns a close handle. + - parseWebPort(value): validates integer ports from 1 through 65535; review re-exports or delegates to this parser. +- **Dependencies**: node:http, node:child_process, route handlers, and the security guard. +- **Reuses**: The current listener, browser opener, and port behavior in src/cli/commands/review.ts. + +### Route security + +- **Purpose**: Reject requests with an invalid Host and protect every POST route with a per-start token and same-origin check. +- **Location**: src/web/security.ts +- **Interfaces**: + - createWebSecurity(boundPort): creates a 32-byte random token and a request guard. + - checkWebRequest(request, routePolicy): returns an allow result or HTTP 403. +- **Dependencies**: node:crypto and node:http request headers. +- **Reuses**: The server receives normalized lower-case header names from Node's IncomingMessage. + +Use a host-only cookie named codedeck_ui_token with HttpOnly, SameSite=Strict, and Path=/. Set it on HTML responses. Compare its value with the token created for this server process. Reject every POST whose Origin is missing or whose HTTP origin does not match the request Host and bound port. The only accepted Host values are 127.0.0.1: and localhost:. Do not add CORS response headers. + +### Home page + +- **Purpose**: List links to Review, Usage, and Setup. +- **Location**: src/web/home-page.ts +- **Interfaces**: HOME_PAGE is a self-contained HTML string. +- **Dependencies**: None. +- **Reuses**: The inline HTML, CSS, and JavaScript style in src/web/review-page.ts. + +### Setup planner + +- **Purpose**: Apply a complete selection set to the resolved config target and return a proposal, diff, and validations without I/O. +- **Location**: src/config/setup-plan.ts +- **Interfaces**: + - buildSetupPlan(input): returns proposedConfig, diff, and validations. + - SetupPlanInput carries the current config read, resolved profile target, complete setup selections, and catalog validation result. + - SetupPlanResult carries the complete proposed config, config diff, and validation details used by SetupEnvelope. +- **Dependencies**: Existing config, profile, role binding, orchestrator, sandbox, autocompact, and model catalog types. +- **Reuses**: Existing config merge rules, profile snapshot helpers, diffConfig, and validateBindings behavior from src/cli/commands/setup.ts. + +Keep file reads, catalog discovery, and writes outside buildSetupPlan. The terminal wizard and setup web handler pass the same selection model to this function. The existing runSetupBatch stays on its current contract. + +When autocompact is turned on, the planner sets enabled=true and keeps other fields. When it is turned off, it sets enabled=false only if the current config already has an autocompact block; otherwise it keeps that block absent. This matches the current wizard behavior. + +### Setup web page + +- **Purpose**: Show the full wizard selection set, catalog state, proposal diff, and apply result. +- **Location**: src/web/setup-page.ts +- **Interfaces**: SETUP_PAGE is a self-contained HTML string that submits JSON to the setup API routes. +- **Dependencies**: Browser fetch, setup catalog and proposal endpoints. +- **Reuses**: The role, effort, orchestrator, sandbox, autocompact, and profile semantics in src/cli/commands/setup.ts. + +### Setup web handlers + +- **Purpose**: Load catalog state, refresh discovery, produce dry-run proposals, and apply validated setup changes. +- **Location**: src/web/setup-routes.ts +- **Interfaces**: + - createSetupRoutes(dependencies): returns GET page/catalog and POST refresh/dry-run/apply routes. + - createSetupHandler(dependencies): exposes the same routes as an HTTP request listener when a command needs only setup. +- **Dependencies**: Config store, model registry, getBatchModels, getCachedOrDiscoverModels, and buildSetupPlan. +- **Reuses**: SetupEnvelope statuses, validation codes, profile targeting, and config persistence. + +Bound request bodies to 64 KiB. A refresh route keeps one in-flight discovery promise and shares it with concurrent refresh callers. Read and validate the current config immediately before the synchronous plan-and-save section so two requests in this process do not both apply a proposal based on an older read. + +For an explicit --profile target with no saved snapshot, use the existing setup target resolution and profile defaults. Do not add a profile selector or profile editor to the page. + +### Usage web page + +- **Purpose**: Display usage totals and every available breakdown, with periodic refresh. +- **Location**: src/web/usage-page.ts +- **Interfaces**: USAGE_PAGE is a self-contained HTML string that serializes selected filters into GET /api/usage. +- **Dependencies**: Browser fetch and the usage API response. +- **Reuses**: The current UsageQueryResult fields and the usage query controls from src/cli/commands/usage.ts. + +### Usage web handler + +- **Purpose**: Translate the browser query into UsageQueryParams and return the CLI usage query result. +- **Location**: src/web/usage-routes.ts +- **Interfaces**: + - createUsageHandler(fetchQuery): returns a GET /api/usage handler. + - parseUsageWebQuery(searchParams, cwd): maps the supported CLI filter names to UsageQueryParams. +- **Dependencies**: An injected fetchUsageQuery function. +- **Reuses**: UsageQueryParams, UsageQueryResult, UsageTotals, and UsageMetricBucket from src/daemon/protocol.ts. + +The handler does not import the CLI command module. Both codedeck usage --web and codedeck ui inject fetchUsageQuery, avoiding a web-to-CLI import cycle. The page checks for byOrigin at runtime so it still renders against results produced before the pending merge. + +Map aggregate query flags with the existing CLI precedence: --all, then --today, then --days, then the default today period when there is no since value. Map 3, 7, and 30 days to their period values; map another positive day count to a local-midnight since value. --current overrides --repo. Keep until, model, and agent filters. + +### CLI wiring + +- **Purpose**: Select the correct root route and keep existing command branches stable. +- **Location**: src/cli/commands/ui.ts, src/cli/commands/review.ts, src/cli/commands/setup.ts, src/cli/commands/usage.ts, and src/cli/index.ts +- **Interfaces**: + - codedeck ui opens the home page. + - codedeck review opens the review page at both / and /review. + - codedeck setup opens /setup unless --tui or batch options select an existing path. + - codedeck usage opens /usage only when --web is supplied and no run ID is present. +- **Dependencies**: Shared server, page constants, and route handlers. +- **Reuses**: Existing commander registration and command option parsing. + +## Data Models + +### Setup selection + +~~~typescript +interface SetupSelection { + agents: Partial> + orchestrator: OrchestratorMode + defaultSandbox?: RunAgentConfig["defaultSandbox"] + autocompact?: RunAgentConfig["autocompact"] +} +~~~ + +Profile target is supplied by the CLI command and is not accepted from the browser request body. A selected profile updates the profile snapshot produced by the existing profile helpers. + +### Setup plan + +~~~typescript +interface SetupPlanInput { + configRead: SetupConfigRead + profile?: string + selections: SetupSelection + catalog: BatchModelsResult +} + +interface SetupPlanResult { + proposedConfig: RunAgentConfig + diff: SetupDiff + validations: SetupValidations +} +~~~ + +SetupDiff and SetupValidations are the pure setup core's result types. The CLI adapts them into the current SetupEnvelope fields. The names can follow nearby code conventions during implementation, but the input and output responsibilities stay fixed. The planner is synchronous and has no file or network dependencies. + +### Usage result + +The route returns the same UsageQueryResult as the CLI. After feat/orchestrator-usage merges, byOrigin is optional and uses UsageMetricBucket[]. Before that merge, the page must render the five current breakdowns without requiring that property. + +## Error Handling Strategy + +| Error scenario | Handling | User impact | +| --- | --- | --- | +| Host, token, or Origin rejected | Return HTTP 403 before route handler invocation. | No protected action runs. | +| Invalid port or listen failure | Print the error and exit with code 1. | No command claims that a server is available. | +| Malformed or oversized setup body | Return HTTP 400 and do not write config. | The page can correct or retry the request. | +| Setup selection validation fails | Return HTTP 422 with the existing validation code and saved=false. | The user sees the failed binding and can revise the selection. | +| Config save fails | Return HTTP 500 with saved=false and the existing save error. | The proposal remains visible and the config is not reported as saved. | +| One model harness cannot be discovered | Return its existing HarnessModels error with other catalog results. | The page can still show catalogs from other harnesses. | +| Usage query fails | Return HTTP 500 with a JSON error; the page keeps the last successful result and shows the error. | Existing IPC and read-only SQLite fallback remain in use. | +| One catalog harness fails discovery | Keep its HarnessModels error in the catalog response beside successful results from other harnesses. | The user can configure roles whose catalogs are available. | + +## Risks & Concerns + +| Concern | Location (file:line) | Impact | Mitigation | +| --- | --- | --- | --- | +| The review command currently combines server lifecycle, routing, port parsing, and browser opening. | src/cli/commands/review.ts:7-135 | A careless extraction can change the existing review URL or command behavior. | Keep review route tests and command tests in the same task that rewires the command. | +| Setup selection and persistence currently live inside a long wizard function. | src/cli/commands/setup.ts:625-801 | Duplicated web planning could drift in merge, profile, or save behavior. | Extract a pure planner, reuse it from the wizard, and cover it with unit tests. | +| The current review server has no Host, token, or Origin checks. | src/cli/commands/review.ts:49-87, :110-124 | Adding action endpoints without a shared guard could expose config writes to forged browser requests. | Guard all routes at the shared server before route dispatch; protect POST routes with token and Origin. | +| Model discovery may take seconds and writes a model cache. | src/cli/commands/setup.ts:634-648; src/core/models.ts:151-190 | A refresh can look frozen or be triggered more than once. | Show a visible discovering state and coalesce concurrent refreshes. | +| Usage origin data is not present at the current HEAD. | src/daemon/protocol.ts:199-211 | P5 will not compile or can hide current usage data if it assumes the pending field. | Sequence P5 after feat/orchestrator-usage merges and render byOrigin conditionally. | +| There are no browser handler tests in the current test set. | tests/review.test.ts, tests/setup-cli-contract.test.ts, tests/usage-query.test.ts | New routes could diverge from CLI behavior without coverage. | Add scoped HTTP integration tests for setup and usage handlers and page tests for HTML behavior. | + +## Tech Decisions + +| Decision | Choice | Rationale | +| --- | --- | --- | +| Home page command | codedeck ui | The home page gets a clear entry while codedeck review keeps its root route. | +| Root routing | Route table has a command-specific root page. | codedeck review can keep / and /review as review pages while codedeck ui uses / for home. | +| Token transport | HttpOnly host-only cookie with SameSite=Strict and Path=/. | Same-origin browser fetch sends it without exposing the token to page JavaScript. | +| Action methods | Use POST for catalog refresh, dry-run, and apply. | Refresh can write the model cache; every action therefore receives the same token and Origin checks. | +| Setup refresh state | The page displays “Discovering models...” while its refresh request is pending. | This uses a normal request and avoids a separate discovery job lifecycle. | +| Usage refresh | Poll the current query every 2 seconds by default. | It matches the existing CLI watch default and replaces the need for --watch in a browser. | +| Usage route dependency | Inject fetchUsageQuery into the web handler. | The same query and fallback logic serve the CLI and browser without a module cycle. | +| Setup --refresh flag | Open /setup and start a protected refresh when the page loads. | This preserves the existing refresh option when setup becomes browser-first. | +| --watch with --web | The browser refreshes itself; --interval changes its timer and --watch does not select terminal rendering. | The user gets a live browser view without the terminal watch loop. | + +## Phase Dependencies + +| Phase | Scope | Depends on | +| --- | --- | --- | +| P1 | Shared server, review compatibility, home page, and codedeck ui | None | +| P2 | Host, token, and Origin enforcement | P1 | +| P3 | Pure setup planner and wizard reuse | P2 | +| P4 | Setup page, handlers, and command wiring | P3 | +| P5 | Usage page and handlers | P4 and merge of feat/orchestrator-usage | + +The required CodeDeck command and page behavior is specified in spec.md. These artifacts stop at planning; implementation and implementation-time verifier reports are outside this docs-only task. diff --git a/.specs/features/web-console/spec.md b/.specs/features/web-console/spec.md new file mode 100644 index 0000000..3e40732 --- /dev/null +++ b/.specs/features/web-console/spec.md @@ -0,0 +1,261 @@ +# Web console specification + +## Problem Statement + +Setup and usage analytics currently require the terminal, while review already runs a local page from the CLI. A browser console will make setup and usage easier to inspect while keeping the existing TUI and machine-readable command paths available with their current behavior. + +## Goals + +- [ ] Serve Review, Usage, and Setup from one local HTTP server started by a CLI command. +- [ ] Keep the server bound to loopback and require token, Host, and Origin checks for actions that change state. +- [ ] Reuse one setup planning function from the existing wizard and the web setup page. +- [ ] Preserve existing setup batch, usage JSON, single-run usage, and statusline contracts. +- [ ] Render usage totals and each supported breakdown in the browser, refreshing the selected query every 2 seconds. + +## Current state (verified) + +- Review currently owns its Node HTTP server, port parser, browser opener, and request handler in src/cli/commands/review.ts:1-135. The default port is 3100, the CLI accepts --port and --no-open, the server binds to 127.0.0.1, GET / and GET /review serve the same page, and GET /api/review loads a read-only review result. Existing route and command tests are in tests/review.test.ts:193-230 and tests/review-command.test.ts:5-27. +- src/web/review-page.ts:8-662 exports one self-contained HTML string. Its CSS starts at line 15 and its JavaScript at line 112. +- Usage query options and the CLI branches are in src/cli/commands/usage.ts:13-30 and :79-214. fetchUsageQuery at :37-77 calls daemon IPC method usage.query and falls back to a read-only SQLite database. The CLI supports date, repository, model, agent, grouping, TUI, watch, plain, and JSON options. A positional run ID uses the separate usage.get path at :103-125, which is used by the statusline contract tests. +- The current UsageQueryResult in src/daemon/protocol.ts:199-211 contains totals and byDay, byRepository, byModel, byAgent, and byRun arrays. It has no byOrigin field at this HEAD. The local feat/orchestrator-usage branch is not an ancestor of this HEAD, so P5 must follow that merge. The usage page must render byOrigin only when the merged result includes it. +- The setup wizard in src/cli/commands/setup.ts:625-801 discovers models, builds role/model and role-effort screens, then applies orchestrator, sandbox, autocompact, and profile selections before saving. The setup parser and batch options are at :804-990, SetupEnvelope is at :1000-1036, runSetupBatch is at :1363-1530, and the command entry is at :1560-1626. The current batch path applies role bindings; orchestrator, sandbox, and autocompact selections are wizard-only. +- Profile targeting in src/cli/commands/setup.ts:831-853 uses an explicit --profile target first, otherwise the active profile. An explicit target without a saved snapshot starts from the existing profile defaults; the wizard writes the selected snapshot at :774-779. +- Model discovery is announced before awaiting results in src/cli/commands/setup.ts:634-648. getCachedOrDiscoverModels in src/core/models.ts:151-190 can return the cache or discover and cache models. +- needsModelSetup is defined in src/cli/commands/setup.ts:437-452 and has no call site under src/. The wizard entry is executeSetupAction in src/cli/commands/setup.ts:1560-1598. + +## Out of Scope + +| Feature | Reason | +| --- | --- | +| Removing or adding features to the setup TUI or usage TUI | Both remain frozen and reachable behind their flags. Removal is a later feature. | +| Moving HTTP into the daemon | The web server lives in the CLI process and only while its command runs. | +| Remote access or authentication beyond the per-start token | The server is local-only and binds to 127.0.0.1. | +| Profile management beyond the existing --profile target | The web setup edits only the target already selected by the CLI. | +| Changing setup batch, usage JSON, or statusline contracts | Batch and agent callers keep their current interfaces and outputs. | +| New review page features | Review keeps its current page and GET API behavior. | +| Build tooling, a frontend framework, or frontend dependencies | Pages remain self-contained HTML strings with inline CSS and JavaScript. | + +--- + +## Assumptions & Open Questions + +| Assumption / decision | Chosen default | Rationale | Confirmed? | +| --- | --- | --- | --- | +| Home page entry command | codedeck ui | This is the requested default and leaves codedeck review at its current root page. | Yes | +| Setup TUI entry | codedeck setup --tui | This is the requested default for keeping the frozen wizard reachable. | Yes | +| Usage web entry | codedeck usage --web | This is the requested opt-in entry and leaves plain usage output unchanged. | Yes | +| Shared web port | 3100 by default; each web entry accepts --port | Review already uses 3100 and has a validated port option. | No | +| No-browser behavior | --no-open or a failed browser opener prints the full URL; the command keeps serving until SIGINT or SIGTERM | It matches the current review command and makes the URL usable in headless environments. | No | +| Host allowlist | Accept only 127.0.0.1: and localhost:, case-insensitively; reject missing or other Host values with HTTP 403 | These are the only browser hostnames supported by the loopback server. | No | +| Per-start token | Generate 32 cryptographically random bytes at server start and set them in a host-only HttpOnly; SameSite=Strict; Path=/ cookie | The browser sends the cookie for same-origin actions without exposing the token to page JavaScript. | No | +| Origin comparison | Require an HTTP Origin whose host and port match the accepted request Host on every POST route; return HTTP 403 when it is absent or different | This blocks cross-origin writes and gives every action route one testable rule. | No | +| Setup catalog refresh | GET /api/setup/catalog reads cached catalog state; POST /api/setup/catalog/refresh starts discovery | Discovery can write the model cache, so the explicit action uses the protected write path. | No | +| Setup --refresh behavior | Open /setup and request protected catalog refresh on page load | This preserves the existing flag while keeping discovery behind the action route. | No | +| Concurrent catalog refresh | Requests made while discovery is running share the same in-flight discovery | Model discovery can take seconds, and duplicate provider calls do not improve the displayed result. | No | +| Setup request size | Accept JSON bodies up to 64 KiB; return HTTP 400 for larger or malformed bodies | A full set of role selections is small; a fixed bound keeps local request parsing bounded. | No | +| Setup API status mapping | Malformed input returns 400, selection validation failures return 422 with a SetupEnvelope, and config save failures return 500 with saved=false | The page gets a predictable transport status while retaining the existing setup validation codes. | No | +| Usage refresh interval | Refresh the current query every 2 seconds; --interval overrides it when supplied with --web | Two seconds matches the current --watch default. | No | +| Usage query failure | Return HTTP 500 with a JSON error, show it in the page, and retain the last successful result | The user can still inspect the last known result while the next refresh retries. | No | +| Usage web with a run ID | A positional run ID or --run keeps the existing single-run branch, even if --web is also present | The statusline and single-run output path must keep their current contract. | No | +| --watch combined with --web | The browser page refreshes on its own; --interval controls its timer and --watch does not change the CLI output path | The web page replaces the need for terminal watch output. | No | +| --tui combined with setup batch flags | Reject the combination with the existing usage-error path | The batch path has a separate non-interactive contract and must not silently open either UI. | No | + +**Open questions:** none. The implementation choices without a human default are recorded above with their rationale. + +## User Stories + +### P1: Shared local server and home page + +**User Story**: As a CodeDeck user, I want one local browser entry point for review, setup, and usage so that I can use those screens without changing their command-line data paths. + +**Why P1**: The shared server and route table are the base for every browser page. + +**Acceptance Criteria**: +1. WHEN a web entry command starts its server THEN the CLI SHALL bind only to 127.0.0.1. WEB-01 +2. WHILE codedeck ui, codedeck review, codedeck setup, or codedeck usage --web is running THEN the server SHALL stay alive until SIGINT or SIGTERM closes it. WEB-02 +3. WHEN a web entry command starts without --port THEN the server SHALL listen on port 3100. WEB-03 +4. IF --port is not an integer from 1 through 65535 THEN the command SHALL report the port error, exit with code 1, and skip server startup. WEB-04 +5. WHEN codedeck review serves GET / THEN the server SHALL return REVIEW_PAGE with HTTP 200 and text/html content type. WEB-05 +6. WHEN codedeck review serves GET /review THEN the server SHALL return the same review page as GET /. WEB-06 +7. WHEN the browser sends GET /api/review THEN the handler SHALL pass ref (default HEAD) and optional file to the existing review loader and return its result. WEB-07 +8. WHEN a user runs codedeck ui THEN GET / SHALL list links to /review, /usage, and /setup. WEB-08 +9. IF --no-open is set or the browser opener fails THEN the command SHALL print the full URL and keep the server running. WEB-09 +10. IF the requested port is already in use THEN the command SHALL print the listen error, exit with code 1, and not print a started-server URL. WEB-57 + +**Independent Test**: Start each command on an ephemeral test port, request its page and API routes, and assert the listen address, status, content type, and links. Stub browser opening to verify the printed URL and server lifetime. + +### P2: Local request security + +**User Story**: As a local user, I want browser actions limited to the local server instance I started so that another site cannot submit setup changes. + +**Why P2**: Setup apply and catalog refresh must reject forged requests before state changes occur. + +**Acceptance Criteria**: +1. IF a request Host is missing or differs from 127.0.0.1: and localhost: THEN the server SHALL return HTTP 403 before route dispatch. WEB-10 +2. WHEN the server starts THEN it SHALL generate a token from 32 cryptographically random bytes. WEB-11 +3. WHEN a POST route is requested THEN the server SHALL require the current token cookie and return HTTP 403 when it is missing, stale, or invalid. WEB-12 +4. WHEN a POST route is requested THEN the server SHALL require an Origin matching the request's HTTP host and bound port, and return HTTP 403 when Origin is absent or different. WEB-13 +5. IF a POST request carries a token cookie from a prior server process THEN the new server SHALL return HTTP 403. WEB-58 + +**Independent Test**: Send accepted and rejected Host, token, and Origin combinations to protected and read routes. Confirm rejected action requests return 403 and do not call their handlers. + +### P3: Shared setup planning + +**User Story**: As a user of either setup interface, I want the same selections to produce the same config proposal and validation result so that the wizard and browser cannot drift. + +**Why P3**: The browser can only match setup behavior after config planning is separated from terminal I/O. + +**Acceptance Criteria**: +1. WHEN the setup planner receives a current config, profile target, complete selections, and catalog validation data THEN it SHALL return a proposed config, diff, and validations without reading or writing files or starting discovery. WEB-14 +2. WHEN the selection contains role bindings THEN the planner SHALL apply each harness, model, and optional effort while preserving roles the selection leaves unchanged and unrelated config keys. WEB-15 +3. WHEN the selection contains an orchestrator mode THEN the planner SHALL put its parameter values in the proposed config without persisting a preset label. WEB-16 +4. WHEN the selection contains a sandbox value THEN the planner SHALL set defaultSandbox to workspace-write or danger-full-access as selected. WEB-17 +5. WHEN the selection turns autocompact on THEN the planner SHALL set autocompact.enabled to true and preserve other autocompact fields. WEB-18 +6. WHEN a profile target is supplied THEN the planner SHALL update only that profile snapshot; when no explicit target is supplied it SHALL use the active profile resolution already used by setup. WEB-19 +7. WHEN the planner returns a result THEN it SHALL expose the proposed config, diff, and validations in fields compatible with the current SetupEnvelope proposal. WEB-20 +8. IF a selected model binding is not accepted by setup validation THEN setup apply SHALL return the existing validation code and SHALL NOT save the proposed config. WEB-21 +9. WHEN the terminal wizard finishes a selection THEN it SHALL use the shared planner while all current setup-wizard and setup-cli-contract tests pass unchanged. WEB-22 +10. WHEN the selection turns autocompact off THEN the planner SHALL set enabled to false if the current config has an autocompact block and SHALL preserve the absent block otherwise. WEB-61 + +**Independent Test**: Call the planner with a global config and a profile config, assert exact proposed values, diff paths, and validation results, and run the existing setup wizard and CLI contract test files without modifying their current cases. + +### P4: Browser setup + +**User Story**: As a user configuring CodeDeck, I want to review a complete setup proposal in a browser before applying it so that I can see the config changes before they are saved. + +**Why P4**: This makes the browser the default setup interface while preserving the batch and TUI paths. + +**Acceptance Criteria**: +1. WHEN the browser requests GET /setup THEN the server SHALL return the self-contained setup page with HTTP 200. WEB-23 +2. WHEN the browser requests GET /api/setup/catalog THEN the handler SHALL return the cached model catalog and its fresh, offline, or unavailable status without starting discovery. WEB-24 +3. WHEN the user requests catalog refresh THEN the page SHALL show “Discovering models...” until the protected refresh response completes. WEB-25 +4. WHILE catalog discovery is in progress THEN concurrent refresh requests SHALL share the same discovery promise. WEB-26 +5. WHEN the browser posts valid selections to /api/setup/dry-run THEN the handler SHALL return a SetupEnvelope-like proposal, diff, and validations. WEB-27 +6. WHEN the browser posts selections to /api/setup/dry-run THEN the handler SHALL leave the config file unchanged. WEB-28 +7. WHEN validated selections with a non-empty diff are posted to /api/setup/apply THEN the handler SHALL save the proposed config and return resultado.status=applied with saved=true. WEB-29 +8. WHEN the proposal diff is empty THEN the apply handler SHALL return resultado.status=unchanged with saved=false and SHALL skip the config write. WEB-30 +9. IF a setup POST body is malformed, larger than 64 KiB, or has an invalid shape THEN the handler SHALL return HTTP 400 without saving config. WEB-31 +10. IF setup validation fails THEN the handler SHALL return HTTP 422 with the existing validation code and saved=false. WEB-32 +11. IF saving the config fails THEN the handler SHALL return HTTP 500 with saved=false and the config error message. WEB-33 +12. WHEN codedeck setup runs without batch flags or --tui THEN it SHALL start the web setup at /setup. WEB-34 +13. WHEN codedeck setup runs with --tui and no batch flags THEN it SHALL run the existing terminal wizard. WEB-35 +14. WHEN codedeck setup runs with existing batch flags THEN it SHALL keep the runSetupBatch JSON, dry-run, bind, exit-code, and save contracts unchanged. WEB-36 +15. WHEN the setup page is rendered THEN it SHALL offer harness and model choices for every role in ROLES. WEB-37 +16. WHEN a selected role supports a reasoning-effort screen THEN the setup page SHALL offer that role's effort choice and SHALL omit the effort control for opencode. WEB-38 +17. WHEN the setup page is rendered THEN it SHALL offer the orchestrator presets and investigate, selfWork, tools, and parallelism parameters. WEB-39 +18. WHEN the setup page is rendered THEN it SHALL offer workspace-write and danger-full-access for sandbox. WEB-40 +19. WHEN the setup page is rendered THEN it SHALL offer autocompact on and off. WEB-41 +20. WHEN setup starts with --profile THEN the page SHALL identify that target and apply its proposal only to that profile. WEB-42 +21. IF model discovery fails for one harness THEN the catalog response SHALL include that harness error with the other catalog results. WEB-59 + +**Independent Test**: Load the setup page, refresh the catalog, submit a dry-run, and assert its proposal and diff. Submit the same valid selection to apply and verify the selected global or profile config changes. Assert malformed, invalid, unchanged, and save-error paths do not claim a save. + +### P5: Browser usage analytics + +**User Story**: As a user reviewing agent costs, I want a browser dashboard with the same filters as the CLI and all available breakdowns so that I can inspect usage without a full-screen terminal. + +**Why P5**: This depends on the pending usage-origin data change and keeps the existing usage TUI and command outputs intact. + +**Acceptance Criteria**: +1. WHEN the browser requests GET /usage THEN the server SHALL return the self-contained usage page with HTTP 200. WEB-43 +2. WHEN GET /api/usage receives filters THEN it SHALL map them to the CLI's UsageQueryParams, including all before today before days precedence, the 3d, 7d, and 30d period mappings, other positive days as local-midnight since, and current overriding repo. WEB-44 +3. WHEN a usage query succeeds THEN the page SHALL display every field in UsageTotals. WEB-45 +4. WHEN a usage query succeeds THEN the page SHALL display the byDay buckets. WEB-46 +5. WHEN a usage query succeeds THEN the page SHALL display the byRepository buckets. WEB-47 +6. WHEN a usage query succeeds THEN the page SHALL display the byModel buckets. WEB-48 +7. WHEN a usage query succeeds THEN the page SHALL display the byAgent buckets. WEB-49 +8. WHEN a usage query succeeds THEN the page SHALL display the byRun buckets. WEB-50 +9. WHERE the merged UsageQueryResult contains byOrigin THEN the page SHALL display the byOrigin buckets. WEB-51 +10. IF UsageQueryResult has no byOrigin field THEN the page SHALL render the other usage sections without an error. WEB-52 +11. WHILE the usage page is open THEN it SHALL refresh the current filters every 2 seconds unless --interval supplied a different interval. WEB-53 +12. WHEN codedeck usage runs with --web and without a run ID THEN it SHALL open /usage with the supplied aggregate filters. WEB-54 +13. WHEN codedeck usage runs without --web THEN it SHALL keep the current snapshot, TUI, watch, plain, and JSON paths unchanged. WEB-55 +14. WHEN codedeck usage receives a positional run ID or --run THEN it SHALL keep the existing usage.get single-run result and statusline contract, including when --web is also present. WEB-56 +15. IF a usage query fails after the page has rendered a result THEN the page SHALL show the query error and retain the last successful result. WEB-60 + +**Independent Test**: Query a fixture result with each current grouping array and optional byOrigin. Assert filter mapping and page output with byOrigin present and absent, then verify the browser refresh uses the same filter values every 2 seconds. + +## Edge Cases + +- IF the requested port is invalid or already in use THEN the command SHALL print the listen error and exit without claiming the server started. +- IF Host, Origin, or the token cookie fails validation THEN the route handler SHALL not run. +- IF model discovery fails for one harness THEN the catalog response SHALL keep that harness error beside other returned catalog entries. +- IF config validation rejects one selected binding THEN apply SHALL save none of the proposal. +- IF the usage query fails after the initial render THEN the page SHALL show the error and retain the last successful usage result. +- IF the server process exits THEN its per-start token SHALL no longer authorize requests. + +## Requirement Traceability + +| Requirement ID | Story | Phase | Status | Task | +| --- | --- | --- | --- | --- | +| WEB-01 | P1: Shared local server and home page | P1 | In Tasks | T1 | +| WEB-02 | P1: Shared local server and home page | P1 | In Tasks | T1 | +| WEB-03 | P1: Shared local server and home page | P1 | In Tasks | T1 | +| WEB-04 | P1: Shared local server and home page | P1 | In Tasks | T1 | +| WEB-05 | P1: Shared local server and home page | P1 | In Tasks | T3 | +| WEB-06 | P1: Shared local server and home page | P1 | In Tasks | T3 | +| WEB-07 | P1: Shared local server and home page | P1 | In Tasks | T3 | +| WEB-08 | P1: Shared local server and home page | P1 | In Tasks | T2, T4, T5 | +| WEB-09 | P1: Shared local server and home page | P1 | In Tasks | T1, T4 | +| WEB-10 | P2: Local request security | P2 | In Tasks | T6, T7 | +| WEB-11 | P2: Local request security | P2 | In Tasks | T6 | +| WEB-12 | P2: Local request security | P2 | In Tasks | T6, T7 | +| WEB-13 | P2: Local request security | P2 | In Tasks | T6, T7 | +| WEB-14 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-15 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-16 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-17 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-18 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-19 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-20 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-21 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-22 | P3: Shared setup planning | P3 | In Tasks | T9 | +| WEB-23 | P4: Browser setup | P4 | In Tasks | T10 | +| WEB-24 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-25 | P4: Browser setup | P4 | In Tasks | T10, T11 | +| WEB-26 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-27 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-28 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-29 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-30 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-31 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-32 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-33 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-34 | P4: Browser setup | P4 | In Tasks | T12 | +| WEB-35 | P4: Browser setup | P4 | In Tasks | T12 | +| WEB-36 | P4: Browser setup | P4 | In Tasks | T12 | +| WEB-37 | P4: Browser setup | P4 | In Tasks | T10 | +| WEB-38 | P4: Browser setup | P4 | In Tasks | T10 | +| WEB-39 | P4: Browser setup | P4 | In Tasks | T10 | +| WEB-40 | P4: Browser setup | P4 | In Tasks | T10 | +| WEB-41 | P4: Browser setup | P4 | In Tasks | T10 | +| WEB-42 | P4: Browser setup | P4 | In Tasks | T10, T12 | +| WEB-43 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-44 | P5: Browser usage analytics | P5 | In Tasks | T14 | +| WEB-45 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-46 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-47 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-48 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-49 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-50 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-51 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-52 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-53 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-54 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-55 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-56 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-57 | P1: Shared local server and home page | P1 | In Tasks | T1 | +| WEB-58 | P2: Local request security | P2 | In Tasks | T6, T7 | +| WEB-59 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-60 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-61 | P3: Shared setup planning | P3 | In Tasks | T8 | + +**Coverage**: 61 total requirements, 61 mapped to tasks, 0 unmapped. + +## Success Criteria + +- [ ] codedeck ui lists Review, Usage, and Setup, and each linked page returns HTTP 200. +- [ ] Invalid Host, Origin, or token requests return HTTP 403 before an action handler runs. +- [ ] Setup dry-run returns a proposal and never writes config; apply writes only validated changes. +- [ ] Existing tests for setup wizard, setup CLI contract, review, usage JSON, and statusline behavior pass without changing their current cases. +- [ ] After feat/orchestrator-usage merges, the usage page renders all available breakdowns and refreshes the active query every 2 seconds by default. diff --git a/.specs/features/web-console/tasks.md b/.specs/features/web-console/tasks.md new file mode 100644 index 0000000..d34e57e --- /dev/null +++ b/.specs/features/web-console/tasks.md @@ -0,0 +1,418 @@ +# Web console tasks + +## Execution Protocol (mandatory) + +Implement these tasks with the tlc-spec-driven skill. Follow its Execute flow, per-task gates, atomic commits, and final verifier. The implementation stays within the source and test files named by each task. Do not change plugin files, add a frontend framework, or run the full Vitest suite. + +--- + +**Design**: .specs/features/web-console/design.md + +**Status**: Draft + +## Test Coverage Matrix + +> Generated from the current Vitest setup, repository instructions, and spec. Tests run under Node and live in tests/. The current project instructions require scoped Vitest commands and prohibit the full suite. + +| Code Layer | Required Test Type | Coverage Expectation | Location Pattern | Run Command | +| --- | --- | --- | --- | --- | +| Web server/router | Integration | Loopback binding, port parsing, route dispatch, review aliases, browser-open fallback, and close behavior | tests/web-server.test.ts, tests/review.test.ts, tests/review-command.test.ts | npx vitest run tests/web-server.test.ts tests/review.test.ts tests/review-command.test.ts | +| Security | Integration | Accepted and rejected Host, token, and Origin combinations; rejected requests never invoke handlers | tests/web-security.test.ts, tests/web-server.test.ts | npx vitest run tests/web-security.test.ts tests/web-server.test.ts | +| Setup core | Unit and integration | Pure planner results for each setup field, profile targeting, diff and validation results, wizard compatibility, and existing setup contracts | tests/setup-plan.test.ts, tests/setup-wizard.test.ts, tests/setup-cli-contract.test.ts | npx vitest run tests/setup-plan.test.ts tests/setup-wizard.test.ts tests/setup-cli-contract.test.ts | +| Setup web handlers | Integration | Catalog reads and refresh, dry-run without writes, apply, unchanged apply, malformed body, validation failure, and save failure | tests/setup-web.test.ts | npx vitest run tests/setup-web.test.ts | +| Usage web handler | Integration | Every supported filter mapping, query success, and IPC plus SQLite error handling | tests/usage-web.test.ts | npx vitest run tests/usage-web.test.ts | +| HTML pages | Unit | Home links, setup controls, visible discovery state, usage totals and breakdowns, optional byOrigin, and refresh interval | tests/web-pages.test.ts | npx vitest run tests/web-pages.test.ts | +| CLI wiring | Command contract | ui, review, setup, and usage flags; setup batch output; usage JSON and single-run statusline behavior | tests/web-cli.test.ts, tests/review-command.test.ts, tests/setup-cli-contract.test.ts, tests/usage-statusline-contract.test.ts | npx vitest run tests/web-cli.test.ts tests/review-command.test.ts tests/setup-cli-contract.test.ts tests/usage-statusline-contract.test.ts | + +## Gate Check Commands + +| Gate Level | When to Use | Command | +| --- | --- | --- | +| Task | After each task | Run the scoped command in that task's Gate field. | +| P1 | After shared server and home wiring | npx vitest run tests/web-server.test.ts tests/web-pages.test.ts tests/review.test.ts tests/review-command.test.ts tests/web-cli.test.ts | +| P2 | After security integration | npx vitest run tests/web-security.test.ts tests/web-server.test.ts | +| P3 | After setup core extraction | npx vitest run tests/setup-plan.test.ts tests/setup-wizard.test.ts tests/setup-cli-contract.test.ts | +| P4 | After setup web wiring | npx vitest run tests/setup-web.test.ts tests/web-pages.test.ts tests/web-cli.test.ts tests/setup-cli-contract.test.ts | +| P5 | After usage web wiring and feat/orchestrator-usage merge | npx vitest run tests/usage-web.test.ts tests/web-pages.test.ts tests/web-cli.test.ts tests/usage-statusline-contract.test.ts | + +## Execution Plan + +Phases run in P1 to P5 order. Tasks run sequentially within each phase. P5 must not start until feat/orchestrator-usage has merged into the implementation base. + +### Phase 1: Shared server and home page + +```text +T1 -> T2 +T1 -> T3 +T1 -> T4 +T2 -> T4 +T3 -> T4 +T4 -> T5 +``` + +### Phase 2: Security + +```text +T1 -> T6 -> T7 +T1 -> T7 +``` + +### Phase 3: Shared setup planner + +```text +T8 -> T9 +``` + +### Phase 4: Setup web + +```text +T8 -> T10 +T9 -> T10 +T7 -> T11 +T8 -> T11 +T9 -> T12 +T10 -> T12 +T11 -> T12 +T12 -> T13 +``` + +### Phase 5: Usage web + +```text +T7 -> T14 +T13 -> T14 -> T15 -> T16 -> T17 +T14 -> T16 +T13 -> T17 +``` + +## Task Breakdown + +### T1: Extract the shared server and router + +**What**: Add the loopback server, route table, port parser, browser opener, URL output, and signal shutdown. +**Where**: src/web/server.ts +**Depends on**: None +**Reuses**: src/cli/commands/review.ts +**Requirement**: WEB-01, WEB-02, WEB-03, WEB-04, WEB-09, WEB-57 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- The server binds to 127.0.0.1 and accepts a validated port from 1 through 65535. +- The default port is 3100. +- A failed browser open or --no-open prints the full URL while the process keeps serving. +- SIGINT and SIGTERM close the server. +- An occupied port prints a listen error and exits with code 1 before reporting a started URL. +- Route tests cover successful dispatch, unknown paths, listen errors, and close behavior. + +**Tests**: Integration, tests/web-server.test.ts +**Gate**: npx vitest run tests/web-server.test.ts + +### T2: Add the home page + +**What**: Add a self-contained home page with links to Review, Usage, and Setup. +**Where**: src/web/home-page.ts +**Depends on**: T1 +**Reuses**: src/web/review-page.ts +**Requirement**: WEB-08 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- The page contains links to /review, /usage, and /setup. +- The page has no external CSS, JavaScript, image, or font dependency. +- Page tests assert each link target. + +**Tests**: Unit, tests/web-pages.test.ts +**Gate**: npx vitest run tests/web-pages.test.ts + +### T3: Rewire the review command + +**What**: Move review server startup to the shared server while preserving the handler exports and current review routes. +**Where**: src/cli/commands/review.ts +**Depends on**: T1 +**Reuses**: src/web/review-page.ts and src/git/review.ts +**Requirement**: WEB-05, WEB-06, WEB-07 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- GET / and GET /review still serve the current review page. +- GET /api/review keeps ref=HEAD as its default and passes file when supplied. +- Existing review command options and exported test seams remain available. +- Existing review route and command tests pass without changing their current assertions. + +**Tests**: Integration, tests/review.test.ts and tests/review-command.test.ts +**Gate**: npx vitest run tests/review.test.ts tests/review-command.test.ts + +### T4: Add the ui command + +**What**: Start the shared server with the home page as root and the review route available. +**Where**: src/cli/commands/ui.ts +**Depends on**: T1, T2, T3 +**Reuses**: Shared server and review handler +**Requirement**: WEB-08, WEB-09 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- codedeck ui opens / by default. +- The route table serves /review. +- --port and --no-open use the shared server options. +- Command tests assert the home path, flags, printed URL, and browser-open failure behavior. + +**Tests**: Command contract, tests/web-cli.test.ts +**Gate**: npx vitest run tests/web-cli.test.ts + +### T5: Register ui with the CLI + +**What**: Register the ui command with the root Commander program. +**Where**: src/cli/index.ts +**Depends on**: T4 +**Reuses**: Existing command registration order in src/cli/index.ts +**Requirement**: WEB-08 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- codedeck --help lists ui. +- CLI tests invoke the registered command. + +**Tests**: Command contract, tests/web-cli.test.ts +**Gate**: npx vitest run tests/web-cli.test.ts + +### T6: Add the request security guard + +**What**: Generate the per-start token and validate Host, token cookie, and Origin. +**Where**: src/web/security.ts +**Depends on**: T1 +**Reuses**: Node request headers and the configured bound port +**Requirement**: WEB-10, WEB-11, WEB-12, WEB-13, WEB-58 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- Each server security instance creates a 32-byte random token. +- HTML responses can set the host-only HttpOnly, SameSite=Strict, Path=/ cookie. +- The guard accepts only the two allowed Host values with the bound port. +- Missing or stale token and missing or mismatched Origin return 403 for POST routes. +- A cookie from a prior server process is rejected by the current security instance. +- Security tests assert that rejected requests do not reach route handlers. + +**Tests**: Integration, tests/web-security.test.ts +**Gate**: npx vitest run tests/web-security.test.ts + +### T7: Apply security before route dispatch + +**What**: Integrate the security guard into the shared server so every request gets a Host check and every POST gets token and Origin checks. +**Where**: src/web/server.ts +**Depends on**: T1, T6 +**Reuses**: Route policies from src/web/security.ts +**Requirement**: WEB-10, WEB-12, WEB-13, WEB-58 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- The guard runs before route dispatch. +- Valid GET routes remain Host-checked and do not require a mutation token. +- Every POST route is rejected with 403 when its token or Origin check fails. +- A token issued by a previous server process cannot authorize a POST. +- Server-level tests cover the guard and route handler call count. + +**Tests**: Integration, tests/web-security.test.ts and tests/web-server.test.ts +**Gate**: npx vitest run tests/web-security.test.ts tests/web-server.test.ts + +### T8: Create the pure setup planner + +**What**: Add a synchronous planner for full role, orchestrator, sandbox, autocompact, and profile selections. +**Where**: src/config/setup-plan.ts +**Depends on**: None +**Reuses**: Existing config and profile helpers, diffConfig, and binding validation +**Requirement**: WEB-14, WEB-15, WEB-16, WEB-17, WEB-18, WEB-19, WEB-20, WEB-21, WEB-61 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- The planner returns the whole proposed config, diff, and validation results. +- It performs no file, network, or discovery I/O. +- Autocompact on sets enabled=true; off sets enabled=false only when the current config already has an autocompact block. +- Tests cover each field, preservation of unselected values, profiles, diff paths, and rejected bindings. +- Rejected bindings cannot produce a saveable success result. + +**Tests**: Unit, tests/setup-plan.test.ts +**Gate**: npx vitest run tests/setup-plan.test.ts + +### T9: Reuse the planner from the terminal wizard + +**What**: Replace wizard-local config assembly with the shared planner and keep the same save boundary. +**Where**: src/cli/commands/setup.ts +**Depends on**: T8 +**Reuses**: Existing wizard selection and persistence flow +**Requirement**: WEB-22 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- The wizard delegates completed selections to the planner. +- Discovery, abort, skip, save-failure, and profile behavior remain unchanged. +- Existing setup wizard and setup CLI contract cases remain unchanged and pass. + +**Tests**: Integration, tests/setup-wizard.test.ts and tests/setup-cli-contract.test.ts +**Gate**: npx vitest run tests/setup-wizard.test.ts tests/setup-cli-contract.test.ts + +### T10: Add the setup page + +**What**: Add the self-contained setup page with all controls represented by the current wizard. +**Where**: src/web/setup-page.ts +**Depends on**: T8, T9 +**Reuses**: Setup selection semantics in src/cli/commands/setup.ts +**Requirement**: WEB-23, WEB-25, WEB-37, WEB-38, WEB-39, WEB-40, WEB-41, WEB-42 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- The page offers every role's harness and model selection. +- It offers effort only for roles whose wizard has an effort screen. +- It offers all orchestrator parameters, both sandbox values, and both autocompact values. +- It displays the selected --profile target. +- Refresh shows “Discovering models...” until the response completes. +- Page tests assert controls, route requests, and no external assets. + +**Tests**: Unit, tests/web-pages.test.ts +**Gate**: npx vitest run tests/web-pages.test.ts + +### T11: Add setup catalog and mutation handlers + +**What**: Implement GET catalog and protected POST refresh, dry-run, and apply handlers. +**Where**: src/web/setup-routes.ts +**Depends on**: T7, T8 +**Reuses**: Config store, getBatchModels, getCachedOrDiscoverModels, and buildSetupPlan +**Requirement**: WEB-24, WEB-25, WEB-26, WEB-27, WEB-28, WEB-29, WEB-30, WEB-31, WEB-32, WEB-33, WEB-59 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- GET catalog returns cached catalog data and status without discovery. +- POST refresh starts discovery and concurrent callers share its promise. +- A discovery error for one harness stays beside the other returned catalog entries. +- Dry-run returns a proposal, diff, and validations without writing. +- Apply writes only a valid non-empty proposal; an empty diff returns unchanged without writing. +- Malformed, oversized, invalid, and save-failure requests return the specified status and envelope. +- HTTP integration tests verify the saved config and every no-write path. + +**Tests**: Integration, tests/setup-web.test.ts +**Gate**: npx vitest run tests/setup-web.test.ts + +### T12: Make web setup the default command path + +**What**: Add setup web flags and route startup while preserving --tui and the existing batch path. +**Where**: src/cli/commands/setup.ts +**Depends on**: T9, T10, T11 +**Reuses**: Existing argument parser and runSetupBatch +**Requirement**: WEB-34, WEB-35, WEB-36, WEB-42 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- codedeck setup without batch flags or --tui opens /setup. +- --tui selects the existing terminal wizard. +- Batch flags still call runSetupBatch and keep JSON, dry-run, binding, exit-code, and save behavior. +- --profile and --refresh reach the intended web setup target and catalog refresh. +- Existing setup CLI contract cases pass without changes. + +**Tests**: Command contract, tests/web-cli.test.ts and tests/setup-cli-contract.test.ts +**Gate**: npx vitest run tests/web-cli.test.ts tests/setup-cli-contract.test.ts + +### T13: Add setup routes to ui + +**What**: Register the setup page and setup API routes in the home command's route table. +**Where**: src/cli/commands/ui.ts +**Depends on**: T12 +**Reuses**: Existing home and review route registrations +**Requirement**: WEB-08, WEB-23 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- codedeck ui serves /setup and its API routes. +- The home page setup link returns the setup page. +- Command tests assert page status and route registration. + +**Tests**: Integration, tests/web-cli.test.ts +**Gate**: npx vitest run tests/web-cli.test.ts + +### T14: Add the usage query handler + +**What**: Translate browser filters into UsageQueryParams and call an injected usage query function. +**Where**: src/web/usage-routes.ts +**Depends on**: T7, T13 +**Reuses**: UsageQueryParams and fetchUsageQuery +**Requirement**: WEB-44 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- The handler maps every supported aggregate filter to the same value used by the CLI. +- --current resolves to the command process working directory. +- Query errors return a JSON error response. +- Tests cover each filter and the successful query result. + +**Tests**: Integration, tests/usage-web.test.ts +**Gate**: npx vitest run tests/usage-web.test.ts + +### T15: Add the usage page + +**What**: Add the self-contained usage page with totals, all current breakdowns, optional byOrigin, and polling. +**Where**: src/web/usage-page.ts +**Depends on**: T14 +**Reuses**: UsageQueryResult and UsageMetricBucket +**Requirement**: WEB-43, WEB-45, WEB-46, WEB-47, WEB-48, WEB-49, WEB-50, WEB-51, WEB-52, WEB-53, WEB-60 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- The page renders every UsageTotals field and each of the five existing breakdown arrays. +- It renders byOrigin when present and renders the other sections when absent. +- It polls the active filter set every 2 seconds by default and honors --interval. +- Query errors leave the last successful result visible and show the error. +- Page tests cover origin-present, origin-absent, polling, and error states. + +**Tests**: Unit, tests/web-pages.test.ts +**Gate**: npx vitest run tests/web-pages.test.ts + +### T16: Add usage --web command wiring + +**What**: Add --web, --port, and --no-open behavior to aggregate usage while preserving CLI output paths. +**Where**: src/cli/commands/usage.ts +**Depends on**: T14, T15 +**Reuses**: Existing usage option parser, fetchUsageQuery, and single-run branch +**Requirement**: WEB-54, WEB-55, WEB-56 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- --web opens /usage and forwards aggregate filters. +- Without --web the existing snapshot, TUI, watch, plain, and JSON paths remain in place. +- Positional and --run IDs keep the current single-run branch, including when --web is also supplied. +- Usage and statusline command contract tests pass unchanged. + +**Tests**: Command contract, tests/web-cli.test.ts and tests/usage-statusline-contract.test.ts +**Gate**: npx vitest run tests/web-cli.test.ts tests/usage-statusline-contract.test.ts + +### T17: Add usage routes to ui + +**What**: Register the usage page and API route in the home command's route table. +**Where**: src/cli/commands/ui.ts +**Depends on**: T13, T16 +**Reuses**: Existing home, review, and setup route registrations +**Requirement**: WEB-08, WEB-43 +**Tools**: MCP none; Skill tlc-spec-driven + +**Done when**: + +- codedeck ui serves /usage and /api/usage. +- The home page usage link returns the usage page. +- Command tests assert page status and route registration. + +**Tests**: Integration, tests/web-cli.test.ts and tests/web-server.test.ts +**Gate**: npx vitest run tests/web-cli.test.ts tests/web-server.test.ts From cd5a30a6d1ca41eb0faa1f0f36951102a9b5aaf9 Mon Sep 17 00:00:00 2001 From: 4ndreello <4ndreello@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:04:26 -0300 Subject: [PATCH 2/3] docs(web): Address web console spec review --- .specs/features/web-console/design.md | 323 +++++++++++-------- .specs/features/web-console/spec.md | 353 ++++++++++++-------- .specs/features/web-console/tasks.md | 448 ++++++++++++++++---------- 3 files changed, 666 insertions(+), 458 deletions(-) diff --git a/.specs/features/web-console/design.md b/.specs/features/web-console/design.md index 383302f..b59de19 100644 --- a/.specs/features/web-console/design.md +++ b/.specs/features/web-console/design.md @@ -6,171 +6,203 @@ --- -## Architecture Overview +## Architecture overview -Each CLI entry starts the same in-process HTTP server with a command-specific root path and route table. The shared router checks Host before dispatch. Action routes also check the per-start token and Origin. The server closes when its CLI command receives SIGINT or SIGTERM. +Each web command starts the shared Node HTTP server in the CLI process. The server binds to 127.0.0.1, obtains its actual port after listen, creates the per-start security instance for that port, then opens or prints a token-bearing URL. The route table owns page registration and supplies its registered page links to the home renderer. Review keeps its existing root and /review aliases. -The UI command serves the home page and all registered pages. Review keeps its current root behavior when invoked as codedeck review. Setup and Usage open their page routes directly. Every page stays a self-contained HTML string. +Every request passes a Host check before route dispatch. POST routes also require the per-port session cookie and a matching HTTP Origin. A valid token query on an HTML GET bootstraps the cookie and redirects to the URL without the query token. The server closes with its owning command. + +Setup uses a pure planner that accepts a RunAgentConfig and selections, then returns a proposal and diff. Config reading, binding validation, and writing remain separate. The web apply path validates only bindings whose harness or model changed. Batch keeps validating only its winning --bind values. The terminal wizard continues accepting typed and off-catalog models without catalog validation. + +Usage shares one pure query parameter builder between the CLI and the web API. The page keeps its view-state, filter, and polling transitions in testable TypeScript functions and injects those function sources into its self-contained HTML string. ~~~mermaid flowchart LR - C[CLI commands] --> S[Shared server and route table] - S --> H[Host check] - H --> O[Origin and token check on POST] - O --> R[Route handler] - R --> RP[Review page and API] - R --> SP[Setup page and API] - R --> UP[Usage page and API] - SP --> P[Pure setup planner] - P --> W[Config writer] - UP --> Q[fetchUsageQuery] + C[CLI commands] --> S[Shared loopback server] + S --> H[Host guard] + H --> O[Token and Origin guard on POST] + O --> R[Registered route table] + R --> P[Self-contained pages] + R --> A[HTTP API handlers] + A --> SP[Setup planner] + A --> V[Separate setup validation] + V --> W[Config writer] + A --> U[Usage query parameter builder] + U --> Q[fetchUsageQuery] Q --> I[Daemon IPC] Q --> D[Read-only SQLite fallback] ~~~ -The route table receives handlers from the CLI commands. This keeps the router independent of setup, usage, and review internals. The server remains in the CLI process and does not start or delegate work to the daemon. - -## Research Notes +## Research notes -The repository already uses Node's built-in HTTP server in review.ts and declares Node 24 or newer in package.json. The official Node 24 HTTP docs cover createServer, listen, request headers, and server close. The official crypto docs cover randomBytes for the per-start token. No HTTP or frontend dependency is needed. +The existing review command uses Node's built-in HTTP server and browser opener. The repository uses Node 24 or newer. The server and token can use node:http and node:crypto without another dependency. - Node.js v24 HTTP documentation: https://nodejs.org/download/release/latest-v24.x/docs/api/http.html - Node.js v24 crypto documentation: https://nodejs.org/download/release/latest-v24.x/docs/api/crypto.html - Context7 MCP was not available in this session. - .specs/STATE.md is absent, so there are no active project decision entries to apply. -## Code Reuse Analysis +## Code reuse analysis ### Existing components to leverage | Component | Location | How to use | | --- | --- | --- | -| Review port parsing, browser opening, and request behavior | src/cli/commands/review.ts:7-135 | Move the shared server concerns into src/web/server.ts and keep review route behavior in the review handler. Preserve its default port and CLI flags. | -| Review HTML | src/web/review-page.ts:8-662 | Serve the existing string without adding assets or changing page behavior. | -| Usage query and fallback | src/cli/commands/usage.ts:37-77 | Inject fetchUsageQuery into the web usage handler so the page uses daemon IPC and the existing read-only SQLite fallback. | -| Usage request and result types | src/daemon/protocol.ts:160-211 | Use UsageQueryParams and UsageQueryResult. Add the optional byOrigin rendering only after feat/orchestrator-usage merges. | -| Setup wizard and selection rules | src/cli/commands/setup.ts:580-801 | Keep the existing wizard, but have it pass completed selections through the shared planner before saving. | -| Setup profile and config helpers | src/config/config.ts:48-76, :103-137, :237-257, :543-548 | Reuse RunAgentConfig, profile snapshot helpers, SetupConfigRead, SetupConfigStore, and saveConfig. | -| Setup proposal and diff behavior | src/cli/commands/setup.ts:1000-1036, :1229-1245, :1257-1332 | Move the pure proposal, diff, and binding validation logic into the shared planner while preserving SetupEnvelope fields and status values. | -| Model discovery | src/core/models.ts:151-190 | Use the cached catalog for GET and invoke getCachedOrDiscoverModels with refresh=true from the protected refresh route. | -| Existing review, setup, and usage tests | tests/review.test.ts, tests/review-command.test.ts, tests/setup-wizard.test.ts, tests/setup-cli-contract.test.ts, tests/usage*.test.ts | Preserve current assertions and add focused tests beside the new server, handlers, pages, and command wiring. | +| Review port parsing, browser opening, and request behavior | src/cli/commands/review.ts:7-135 | Move server startup, port parsing, and browser opening into src/web/server.ts. Keep review data handling and both existing page routes. | +| Review page | src/web/review-page.ts:8-662 | Serve the current self-contained HTML string without adding assets. | +| Setup wizard selections | src/cli/commands/setup.ts:499-801 | Keep its harness, model, effort, orchestrator, sandbox, autocompact, skip, and profile choices. Pass completed selections through the planner before saving. | +| Picker free-text behavior | src/cli/picker-state.ts:113-141 | Preserve typed harness:model values, including models absent from the catalog. | +| Profile target resolution | src/cli/commands/setup.ts:831-853 | Use the current explicit and active profile rules for setup state and planning. | +| Setup envelope and batch validation | src/cli/commands/setup.ts:1000-1036, 1402-1459 | Preserve the exact envelope fields and keep batch validation on winning --bind values outside the planner. | +| Setup config reads | src/config/config.ts:391-405, 531-540 | Use readConfigForSetup for web state and apply so invalid JSON and read errors are not replaced by default config. Do not use loadConfig for apply. | +| Setup diffs and profile snapshots | src/config/config.ts:48-76, 103-137, 237-257, 543-548 | Use RunAgentConfig, existing profile helpers, and diffConfig to produce and save the same config shape. | +| Model catalog | src/core/models.ts:276-389 | Use getBatchModels for catalog reads and refresh. | +| Usage query and fallback | src/cli/commands/usage.ts:37-77 | Reuse fetchUsageQuery so CLI and web keep daemon usage.query and read-only SQLite fallback behavior. | +| Usage query and result types | src/daemon/protocol.ts:160-220 | Use UsageQueryParams and UsageQueryResult. The merged type requires byOrigin; tolerate its absence at runtime for an older daemon over IPC. | +| Existing command and behavior tests | tests/review.test.ts, tests/review-command.test.ts, tests/setup-wizard.test.ts, tests/setup-cli-contract.test.ts, tests/usage-cli.test.ts | Preserve current contract assertions and add focused tests for new server, page logic, route handlers, and CLI options. | +| Setup README section | README.md:149-186 | Replace the picker-first description with web setup as the default and retain --tui and --refresh guidance. | ### Integration points | System | Integration method | | --- | --- | -| CLI command registration | Add codedeck ui in src/cli/index.ts; keep web startup in the existing review, setup, and usage commands. | -| Review data | Keep GET /api/review read-only and delegate to the current local review loader. | -| Setup config | The handler reads through the existing config store, calls the pure planner, then saves only after validation passes. | -| Setup model catalog | GET returns cached status and models. A protected POST starts or joins discovery and shows the page's discovering state while it is pending. | -| Usage analytics | The handler translates URL filters to UsageQueryParams and calls the same fetchUsageQuery used by the CLI. | -| Daemon | No changes. Usage continues to use the current IPC query and SQLite fallback from the CLI process. | +| CLI command registry | Register codedeck ui while preserving the current review, setup, and usage command contracts. | +| Review data | Keep GET /api/review read-only and delegate to the current review loader. | +| Setup config | Read with readConfigForSetup, resolve the target, plan from RunAgentConfig, validate changed bindings separately, then save only a valid proposal. | +| Setup catalog | GET calls getBatchModels with allowNetwork:false. Protected refresh calls it with refresh:true, allowNetwork:true, and timeoutMs:12000. | +| Usage query | Convert URL filters to the options accepted by buildUsageQueryParams, then inject the result into fetchUsageQuery. | +| Daemon | No changes. HTTP stays in the CLI process; usage keeps its existing IPC and SQLite fallback. | ## Components ### Shared web server -- **Purpose**: Start a loopback-only Node HTTP server, dispatch a route table, print the URL, and close with the command. +- **Purpose**: Start a loopback server, dispatch registered routes, open or print its URL, and close it with the CLI command. - **Location**: src/web/server.ts - **Interfaces**: - - createWebServer(options): creates the route listener with a command-specific root page. - - startWebServer(options): listens on 127.0.0.1, opens the requested URL unless --no-open is set, and returns a close handle. - - parseWebPort(value): validates integer ports from 1 through 65535; review re-exports or delegates to this parser. -- **Dependencies**: node:http, node:child_process, route handlers, and the security guard. -- **Reuses**: The current listener, browser opener, and port behavior in src/cli/commands/review.ts. + - createWebServer(options) creates a listener from a route table and injectable server dependencies. + - startWebServer(options) listens on 127.0.0.1 and returns its actual address, URL, and close operation. + - parseWebPort(value) accepts integer user ports from 1 through 65535. +- **Dependencies**: node:http, node:child_process, page and API route handlers, and the security guard. +- **Reuses**: Current listener, browser opener, and port behavior in src/cli/commands/review.ts. + +The injected server seam may listen on port 0 for tests. After listen, read server.address().port and pass that actual port to createWebSecurity before opening or printing the URL. The CLI parser still rejects port 0. Inject both server close and process exit functions so signal handling can be tested without process.exit. + +The browser opener and --no-open output use the initial route URL with ?t=. If opening fails, print that full URL and keep serving. SIGINT and SIGTERM close the server, then call the injected exit function. An occupied port reports the listen error and never reports a started URL. -### Route security +The route table identifies page routes separately from API routes and may attach a navigation label to a page. codedeck ui renders links only from its registered page routes. The review command selects the review page at both / and /review; codedeck ui selects the home page at /. -- **Purpose**: Reject requests with an invalid Host and protect every POST route with a per-start token and same-origin check. +### Request security + +- **Purpose**: Check Host on every request and protect each mutating route with a token cookie and Origin validation. - **Location**: src/web/security.ts - **Interfaces**: - - createWebSecurity(boundPort): creates a 32-byte random token and a request guard. - - checkWebRequest(request, routePolicy): returns an allow result or HTTP 403. -- **Dependencies**: node:crypto and node:http request headers. -- **Reuses**: The server receives normalized lower-case header names from Node's IncomingMessage. + - createWebSecurity(boundPort) creates one cryptographically random 32-byte token after listen. + - checkWebRequest(request, routePolicy) returns an allow result or HTTP 403. + - getTokenUrl(url, token) adds the out-of-band t query parameter to an initial HTML page URL. +- **Dependencies**: node:crypto and Node request headers. +- **Reuses**: Node's normalized IncomingMessage headers and the port resolved by the server. + +Accept only Host values 127.0.0.1: and localhost:, compared case-insensitively. Every POST requires an HTTP Origin whose host and port match that request Host and the bound port. A missing or mismatched Host, cookie, or Origin returns 403 before the route handler runs. + +Only an HTML GET with the valid t query token sets the host-only HttpOnly cookie named codedeck_ui_token_, with SameSite=Strict and Path=/. That response uses HTTP 303 and redirects to the same path without t. An HTML GET without a valid token does not set the cookie. The cookie value is checked against the current server token on every POST. Do not add CORS response headers. -Use a host-only cookie named codedeck_ui_token with HttpOnly, SameSite=Strict, and Path=/. Set it on HTML responses. Compare its value with the token created for this server process. Reject every POST whose Origin is missing or whose HTTP origin does not match the request Host and bound port. The only accepted Host values are 127.0.0.1: and localhost:. Do not add CORS response headers. +Every HTML response includes Content-Security-Policy: frame-ancestors 'none'. This applies to the home, review, setup, and usage pages. ### Home page -- **Purpose**: List links to Review, Usage, and Setup. +- **Purpose**: List links to pages registered by the active route table. - **Location**: src/web/home-page.ts -- **Interfaces**: HOME_PAGE is a self-contained HTML string. -- **Dependencies**: None. -- **Reuses**: The inline HTML, CSS, and JavaScript style in src/web/review-page.ts. +- **Interfaces**: renderHomePage(pageRoutes) returns a self-contained HTML string for the passed route labels and paths. +- **Dependencies**: Registered page route metadata. +- **Reuses**: Inline HTML and CSS in src/web/review-page.ts. + +The home page does not hard-code future routes. The initial ui command links only the page routes it registers. Adding the setup and usage routes adds their links through the same route metadata. ### Setup planner -- **Purpose**: Apply a complete selection set to the resolved config target and return a proposal, diff, and validations without I/O. +- **Purpose**: Apply setup selections to an already-resolved RunAgentConfig and return a proposed config and diff without I/O or catalog validation. - **Location**: src/config/setup-plan.ts - **Interfaces**: - - buildSetupPlan(input): returns proposedConfig, diff, and validations. - - SetupPlanInput carries the current config read, resolved profile target, complete setup selections, and catalog validation result. - - SetupPlanResult carries the complete proposed config, config diff, and validation details used by SetupEnvelope. -- **Dependencies**: Existing config, profile, role binding, orchestrator, sandbox, autocompact, and model catalog types. -- **Reuses**: Existing config merge rules, profile snapshot helpers, diffConfig, and validateBindings behavior from src/cli/commands/setup.ts. - -Keep file reads, catalog discovery, and writes outside buildSetupPlan. The terminal wizard and setup web handler pass the same selection model to this function. The existing runSetupBatch stays on its current contract. + - buildSetupPlan(currentConfig, targetProfile, selections) returns proposedConfig and diff. + - SetupSelection represents selected bindings, effort values, orchestrator mode and parameters, sandbox, and autocompact. +- **Dependencies**: RunAgentConfig, Role, role binding, orchestrator, sandbox, and autocompact types; profile snapshot helpers; diffConfig. +- **Reuses**: Existing setup merge rules and config diff behavior. -When autocompact is turned on, the planner sets enabled=true and keeps other fields. When it is turned off, it sets enabled=false only if the current config already has an autocompact block; otherwise it keeps that block absent. This matches the current wizard behavior. +The planner accepts a RunAgentConfig, never SetupConfigRead. It performs no filesystem, network, model discovery, or catalog validation. The terminal wizard and web flow pass the same selection model into it. The wizard keeps its current typed-model behavior and does not gain catalog validation. -### Setup web page +Profile resolution and config reading happen before planning. An explicit profile uses the existing profile snapshot rules. An absent active profile keeps the existing SetupUsageError. A first-run target with all roles skipped keeps agents: {} as the empty sentinel. Turning autocompact off writes enabled=false only when the target already has an autocompact block. -- **Purpose**: Show the full wizard selection set, catalog state, proposal diff, and apply result. -- **Location**: src/web/setup-page.ts -- **Interfaces**: SETUP_PAGE is a self-contained HTML string that submits JSON to the setup API routes. -- **Dependencies**: Browser fetch, setup catalog and proposal endpoints. -- **Reuses**: The role, effort, orchestrator, sandbox, autocompact, and profile semantics in src/cli/commands/setup.ts. +The planner does not produce binding validation results. Batch continues to validate only the winning --bind value for each role. Web apply compares each selected binding with the resolved target, validates only bindings whose harness or model changed, and does not validate effort-only changes. As a result, an unchanged off-catalog binding does not block a sandbox or other unrelated change. -### Setup web handlers +### Setup page and API -- **Purpose**: Load catalog state, refresh discovery, produce dry-run proposals, and apply validated setup changes. -- **Location**: src/web/setup-routes.ts +- **Purpose**: Show the current target and all wizard selections, produce a dry-run proposal, and save only a validated proposal. +- **Page location**: src/web/setup-page.ts +- **API location**: src/web/setup-routes.ts - **Interfaces**: - - createSetupRoutes(dependencies): returns GET page/catalog and POST refresh/dry-run/apply routes. - - createSetupHandler(dependencies): exposes the same routes as an HTTP request listener when a command needs only setup. -- **Dependencies**: Config store, model registry, getBatchModels, getCachedOrDiscoverModels, and buildSetupPlan. -- **Reuses**: SetupEnvelope statuses, validation codes, profile targeting, and config persistence. + - SETUP_PAGE is a self-contained HTML string with inline CSS and injected setup page behavior. + - createSetupRoutes(dependencies) returns the setup page, catalog, state, refresh, dry-run, and apply route definitions. + - buildSetupState(configRead, profileOption) returns the resolved target and current values needed for prefill. +- **Dependencies**: readConfigForSetup, resolveSetupTarget, getBatchModels, separate binding validation, buildSetupPlan, and the config writer. +- **Reuses**: Existing setup config, profile, diff, validation codes, and SetupEnvelope. -Bound request bodies to 64 KiB. A refresh route keeps one in-flight discovery promise and shares it with concurrent refresh callers. Read and validate the current config immediately before the synchronous plan-and-save section so two requests in this process do not both apply a proposal based on an older read. +GET /api/setup/state reads with readConfigForSetup and reports whether the resolved target is global or a named profile. It returns current bindings, per-role effort, orchestrator, sandbox, and autocompact values. An active profile name without a snapshot returns the existing SetupUsageError. An explicit profile without a saved snapshot uses the current profile defaults. -For an explicit --profile target with no saved snapshot, use the existing setup target resolution and profile defaults. Do not add a profile selector or profile editor to the page. +GET /api/setup/catalog calls getBatchModels with allowNetwork:false and returns its models, status, source, ageMs, cacheWriteFailed, and discoveryError when present. The protected POST /api/setup/catalog/refresh calls getBatchModels with refresh:true, allowNetwork:true, and timeoutMs:12000. If discovery is incomplete or a requested harness reports an error, getBatchModels returns its cache fallback and discoveryError without partial network results. Concurrent refresh requests share one in-flight promise. -### Usage web page +Dry-run calls the planner and separate validator, then returns an object with the exact SetupEnvelope fields proposta, validacoes, mudancas, and resultado. It never writes config. Apply uses the same proposal and validation steps, and writes only if the diff is non-empty and every changed binding validates. An empty diff returns unchanged without a write. -- **Purpose**: Display usage totals and every available breakdown, with periodic refresh. -- **Location**: src/web/usage-page.ts -- **Interfaces**: USAGE_PAGE is a self-contained HTML string that serializes selected filters into GET /api/usage. -- **Dependencies**: Browser fetch and the usage API response. -- **Reuses**: The current UsageQueryResult fields and the usage query controls from src/cli/commands/usage.ts. +The route reads config through readConfigForSetup, never loadConfig. Invalid JSON returns resultado.code=14 and a read error returns resultado.code=15, with no write in either case. Malformed or oversized requests return HTTP 400. Validation failures return HTTP 422 with the existing code and saved=false. Save failures return HTTP 500 with saved=false. The setup API has one route factory, createSetupRoutes. -### Usage web handler +Setup selection, error rendering, and refresh state live in pure TypeScript page behavior functions. The HTML string injects those same function sources with Function.prototype.toString(). Node tests call the functions with fake fetch and timer adapters, then assert state changes and requests directly. HTML substring checks may cover static markup but do not stand in for these behavior tests. A protected POST 403 changes page state to the reload/restart message from WEB-73. -- **Purpose**: Translate the browser query into UsageQueryParams and return the CLI usage query result. -- **Location**: src/web/usage-routes.ts +### Usage query parameter builder + +- **Purpose**: Produce identical UsageQueryParams for the CLI and the web API from the same options, working directory, and clock value. +- **Location**: src/core/usage-query.ts +- **Interfaces**: + - buildUsageQueryParams(opts, cwd, now) returns UsageQueryParams. +- **Dependencies**: UsageQueryParams and UsagePeriod from src/daemon/protocol.ts. +- **Reuses**: The aggregation logic currently in src/cli/commands/usage.ts:129-164. + +The CLI passes parsed options, process.cwd(), and the current date. The web handler converts its query string to the same option shape and passes its working directory and current date to the builder. The builder keeps the existing precedence: --all, --today, --days, then default today when since is absent. It maps 3, 7, and 30 to named periods, other positive day counts to a local-midnight since value, lets --current override --repo, and passes through since, until, model, and agent. + +### Usage page and API + +- **Purpose**: Return aggregate usage for browser filters and display results with all available breakdowns. +- **Page location**: src/web/usage-page.ts +- **API location**: src/web/usage-routes.ts - **Interfaces**: - - createUsageHandler(fetchQuery): returns a GET /api/usage handler. - - parseUsageWebQuery(searchParams, cwd): maps the supported CLI filter names to UsageQueryParams. -- **Dependencies**: An injected fetchUsageQuery function. + - USAGE_PAGE is a self-contained HTML string with inline CSS and injected usage page behavior. + - createUsageRoutes(dependencies) returns GET /usage and GET /api/usage handlers. + - parseUsageWebQuery(searchParams, cwd) maps the URL fields to the option shape for buildUsageQueryParams. +- **Dependencies**: buildUsageQueryParams and injected fetchUsageQuery. - **Reuses**: UsageQueryParams, UsageQueryResult, UsageTotals, and UsageMetricBucket from src/daemon/protocol.ts. -The handler does not import the CLI command module. Both codedeck usage --web and codedeck ui inject fetchUsageQuery, avoiding a web-to-CLI import cycle. The page checks for byOrigin at runtime so it still renders against results produced before the pending merge. +The endpoint returns the same UsageQueryResult as the aggregate CLI. After feat/orchestrator-usage merges, byOrigin is required by the TypeScript type. The page reads result.byOrigin ?? [] at runtime because an older daemon over IPC may omit the property. -Map aggregate query flags with the existing CLI precedence: --all, then --today, then --days, then the default today period when there is no since value. Map 3, 7, and 30 days to their period values; map another positive day count to a local-midnight since value. --current overrides --repo. Keep until, model, and agent filters. +The page has controls for period, repo, model, agent, since, and until. A change to any control re-queries with the current filter set. The selected --by value is the initial highlighted breakdown; the page keeps every breakdown section accessible. It shows every UsageTotals field and the byDay, byRepository, byModel, byAgent, byRun, and when present byOrigin arrays. + +The page behavior functions own query state, filter changes, polling, render data, and error state. The HTML injects the same function source that Node tests import and call with fake fetch, timers, and render callbacks. Tests assert re-query-on-change, interval normalization, polling, origin-present and origin-absent results, and retention of the last good result after a failed query. + +Polling uses Math.max(1, Number(opts.interval) || 2). Commander supplies the default string "2", so the implementation cannot distinguish an omitted option from explicit --interval 2. Zero and NaN resolve to 2 seconds; negative values and positive values below 1 resolve to 1 second. For aggregate --web calls, the page refreshes itself; --watch does not select terminal rendering. A positional run ID or --run stays on usage.get and starts no server even with --web. --backfill also runs before web startup. --observe and --json retain their post-merge single-run behavior. ### CLI wiring -- **Purpose**: Select the correct root route and keep existing command branches stable. -- **Location**: src/cli/commands/ui.ts, src/cli/commands/review.ts, src/cli/commands/setup.ts, src/cli/commands/usage.ts, and src/cli/index.ts +- **Purpose**: Select the matching command path and preserve existing non-web contracts. +- **Locations**: src/cli/commands/ui.ts, src/cli/commands/review.ts, src/cli/commands/setup.ts, src/cli/commands/usage.ts, and src/cli/index.ts - **Interfaces**: - - codedeck ui opens the home page. - - codedeck review opens the review page at both / and /review. - - codedeck setup opens /setup unless --tui or batch options select an existing path. - - codedeck usage opens /usage only when --web is supplied and no run ID is present. -- **Dependencies**: Shared server, page constants, and route handlers. -- **Reuses**: Existing commander registration and command option parsing. + - codedeck ui starts the home route table. + - codedeck review keeps /, /review, and GET /api/review. + - Interactive codedeck setup with a TTY opens /setup unless --tui selects the frozen wizard. Without both TTYs, no-batch setup keeps its current exit code 1 and terminal message. + - Setup batch flags continue to call runSetupBatch with their current output and status contracts. + - Aggregate codedeck usage opens /usage only with --web. + - When --backfill is absent, positional or --run usage IDs keep usage.get and do not start the server, including usage --web --json. --by origin remains aggregate-only. +- **Dependencies**: Shared server, route factories, page constants, query builder, and existing CLI parser. +- **Reuses**: Existing Commander registrations and branches. -## Data Models +## Data models ### Setup selection @@ -178,82 +210,91 @@ Map aggregate query flags with the existing CLI precedence: --all, then --today, interface SetupSelection { agents: Partial> orchestrator: OrchestratorMode - defaultSandbox?: RunAgentConfig["defaultSandbox"] + sandbox?: RunAgentConfig["defaultSandbox"] autocompact?: RunAgentConfig["autocompact"] } ~~~ -Profile target is supplied by the CLI command and is not accepted from the browser request body. A selected profile updates the profile snapshot produced by the existing profile helpers. +Missing role entries mean the user skipped that role. A profile name is supplied by CLI target resolution and is never accepted from the browser request body. A first-run config with no selected roles retains the agents: {} sentinel. ### Setup plan ~~~typescript -interface SetupPlanInput { - configRead: SetupConfigRead - profile?: string - selections: SetupSelection - catalog: BatchModelsResult -} - interface SetupPlanResult { proposedConfig: RunAgentConfig diff: SetupDiff - validations: SetupValidations } ~~~ -SetupDiff and SetupValidations are the pure setup core's result types. The CLI adapts them into the current SetupEnvelope fields. The names can follow nearby code conventions during implementation, but the input and output responsibilities stay fixed. The planner is synchronous and has no file or network dependencies. +The current config input is RunAgentConfig. Catalog validation and SetupConfigRead are not planner inputs. The CLI route adapts the result, separate validation result, and config status into the existing SetupEnvelope fields. ### Usage result -The route returns the same UsageQueryResult as the CLI. After feat/orchestrator-usage merges, byOrigin is optional and uses UsageMetricBucket[]. Before that merge, the page must render the five current breakdowns without requiring that property. +The route returns UsageQueryResult. In the post-merge type, byOrigin is required and contains UsageMetricBucket[]. At runtime, the page treats an absent field as an empty origin breakdown to support an older daemon process. -## Error Handling Strategy +## Error handling strategy | Error scenario | Handling | User impact | | --- | --- | --- | | Host, token, or Origin rejected | Return HTTP 403 before route handler invocation. | No protected action runs. | -| Invalid port or listen failure | Print the error and exit with code 1. | No command claims that a server is available. | -| Malformed or oversized setup body | Return HTTP 400 and do not write config. | The page can correct or retry the request. | -| Setup selection validation fails | Return HTTP 422 with the existing validation code and saved=false. | The user sees the failed binding and can revise the selection. | -| Config save fails | Return HTTP 500 with saved=false and the existing save error. | The proposal remains visible and the config is not reported as saved. | -| One model harness cannot be discovered | Return its existing HarnessModels error with other catalog results. | The page can still show catalogs from other harnesses. | -| Usage query fails | Return HTTP 500 with a JSON error; the page keeps the last successful result and shows the error. | Existing IPC and read-only SQLite fallback remain in use. | -| One catalog harness fails discovery | Keep its HarnessModels error in the catalog response beside successful results from other harnesses. | The user can configure roles whose catalogs are available. | - -## Risks & Concerns +| Stale session after server restart | Return HTTP 403; page shows the reload/restart message. | The user opens the new token URL if reload does not restore the session. | +| Invalid port or listen failure | Print the error and exit with code 1. | The command does not claim that a server is available. | +| Malformed or oversized setup body | Return HTTP 400 and do not write config. | The user can correct or retry the request. | +| Invalid setup config JSON | Return code 14 and do not write. | Existing invalid config remains available for repair. | +| Setup config read error | Return code 15 and do not write. | The user sees the original read error. | +| Changed binding validation fails | Return HTTP 422 with the existing validation code and saved=false. | The page can identify the changed binding that failed. | +| Config save fails | Return HTTP 500 with saved=false and the existing save error. | The server does not report that the proposal was saved. | +| Catalog discovery is incomplete or errors | Return getBatchModels fallback and discoveryError, without partial network results. | The page shows the actual cache or unavailable state. | +| Usage query fails | Return HTTP 500 with JSON error; preserve the last successful page result. | A later poll or filter change can recover. | + +## Risks & concerns | Concern | Location (file:line) | Impact | Mitigation | | --- | --- | --- | --- | -| The review command currently combines server lifecycle, routing, port parsing, and browser opening. | src/cli/commands/review.ts:7-135 | A careless extraction can change the existing review URL or command behavior. | Keep review route tests and command tests in the same task that rewires the command. | -| Setup selection and persistence currently live inside a long wizard function. | src/cli/commands/setup.ts:625-801 | Duplicated web planning could drift in merge, profile, or save behavior. | Extract a pure planner, reuse it from the wizard, and cover it with unit tests. | -| The current review server has no Host, token, or Origin checks. | src/cli/commands/review.ts:49-87, :110-124 | Adding action endpoints without a shared guard could expose config writes to forged browser requests. | Guard all routes at the shared server before route dispatch; protect POST routes with token and Origin. | -| Model discovery may take seconds and writes a model cache. | src/cli/commands/setup.ts:634-648; src/core/models.ts:151-190 | A refresh can look frozen or be triggered more than once. | Show a visible discovering state and coalesce concurrent refreshes. | -| Usage origin data is not present at the current HEAD. | src/daemon/protocol.ts:199-211 | P5 will not compile or can hide current usage data if it assumes the pending field. | Sequence P5 after feat/orchestrator-usage merges and render byOrigin conditionally. | -| There are no browser handler tests in the current test set. | tests/review.test.ts, tests/setup-cli-contract.test.ts, tests/usage-query.test.ts | New routes could diverge from CLI behavior without coverage. | Add scoped HTTP integration tests for setup and usage handlers and page tests for HTML behavior. | +| The review command combines server lifecycle, routing, port parsing, and browser opening. | src/cli/commands/review.ts:7-135 | A careless extraction can change the current review behavior. | Keep review route and command tests in the rewiring task. | +| Setup selection and persistence live in a long wizard command. | src/cli/commands/setup.ts:625-801 | Duplicated planning could change profiles, skips, or config merge behavior. | Extract one pure planner and test it against current setup behavior. | +| loadConfig swallows read and parse errors. | src/config/config.ts:531-540 | Web apply could treat invalid config as defaults and overwrite it. | Use readConfigForSetup and test codes 14 and 15 before any write. | +| The current review handler calls process.exit during shutdown. | src/cli/commands/review.ts:128-131 | Signal handling is hard to assert and can terminate a test runner. | Inject close and exit functions into the shared server. | +| Model discovery can return incomplete results after network errors. | src/core/models.ts:329-389 | A UI that displays partial provider data would imply a complete catalog. | Use getBatchModels only and render its fallback plus discoveryError. | +| The post-merge usage type requires byOrigin, but an older daemon can answer IPC without the field. | src/daemon/protocol.ts:220; feat/orchestrator-usage | Direct access can crash page rendering against that daemon. | Keep the type required and use a runtime fallback in the page. | +| Page behavior currently has no DOM test dependency. | tests/review.test.ts, tests/setup-wizard.test.ts | Static markup checks cannot prove polling or state transitions. | Test exported pure page functions with fake fetch and timers in Node. | -## Tech Decisions +## Tech decisions | Decision | Choice | Rationale | | --- | --- | --- | -| Home page command | codedeck ui | The home page gets a clear entry while codedeck review keeps its root route. | -| Root routing | Route table has a command-specific root page. | codedeck review can keep / and /review as review pages while codedeck ui uses / for home. | -| Token transport | HttpOnly host-only cookie with SameSite=Strict and Path=/. | Same-origin browser fetch sends it without exposing the token to page JavaScript. | -| Action methods | Use POST for catalog refresh, dry-run, and apply. | Refresh can write the model cache; every action therefore receives the same token and Origin checks. | -| Setup refresh state | The page displays “Discovering models...” while its refresh request is pending. | This uses a normal request and avoids a separate discovery job lifecycle. | -| Usage refresh | Poll the current query every 2 seconds by default. | It matches the existing CLI watch default and replaces the need for --watch in a browser. | -| Usage route dependency | Inject fetchUsageQuery into the web handler. | The same query and fallback logic serve the CLI and browser without a module cycle. | -| Setup --refresh flag | Open /setup and start a protected refresh when the page loads. | This preserves the existing refresh option when setup becomes browser-first. | -| --watch with --web | The browser refreshes itself; --interval changes its timer and --watch does not select terminal rendering. | The user gets a live browser view without the terminal watch loop. | - -## Phase Dependencies +| Home command | codedeck ui | It gives the route table a dedicated entry and keeps review at its current root. | +| Home links | Build from registered page routes. | P1 must not advertise a page that is not registered until its phase is complete. | +| Shared port | Default 3100; user values 1 through 65535; test seam may bind port 0. | This preserves review behavior while allowing ephemeral integration-test ports. | +| Bound port | Read server.address().port after listen, then create security with that value. | Host checks and cookie names must use the actual bound port. | +| Shutdown | Inject close and exit functions; close before exit. | Signal tests can assert both operations without terminating Vitest. | +| Token | 32 cryptographically random bytes, sent only in the initial URL query. | The page does not need to embed the token in HTML or JavaScript. | +| Token cookie | codedeck_ui_token_, host-only, HttpOnly, SameSite=Strict, Path=/. | Cookie storage does not isolate by port, so each server port gets a distinct name. | +| Token bootstrap | On valid token HTML GET, set the cookie and return HTTP 303 to the same route without t. | This removes the token from the visible URL after establishing the session. | +| Action security | Host-check every route; require cookie and matching HTTP Origin on every POST. | Read routes remain locally constrained and all mutating routes share one rule. | +| HTML framing | Add Content-Security-Policy: frame-ancestors 'none' to every HTML response. | Local pages should not be embedded by another origin. | +| Catalog source | Use getBatchModels for both GET and refresh. | One helper defines cache, discovery timeout, incomplete-result, and fallback behavior. | +| Planner boundary | Accept RunAgentConfig and selections; return proposed config and diff only. | Validation depends on the selected catalog and belongs in separate callers. | +| Validation | Web validates changed harness/model pairs only; batch validates winning --bind entries; wizard adds no catalog validation. | This keeps unchanged off-catalog config and existing wizard behavior intact. | +| Page tests | Export behavior functions from the page module and inject their source into HTML. | Vitest can test actual state logic under Node without a DOM package. | +| Usage query params | Extract buildUsageQueryParams into src/core/usage-query.ts and call it from CLI and web. | A pure shared function makes filter mapping and parity testable. | +| Usage origin | Require byOrigin in the post-merge type; handle a missing runtime field from an older daemon. | Compile-time post-merge contracts and runtime daemon compatibility both remain explicit. | +| Aggregate usage --json | With --web, open /usage and print the token URL; retain JSON for aggregate calls without --web and for single-run calls. | The web option selects the aggregate interface while run-id callers retain their exact JSON path. | +| Usage --by | Use --by as the initially highlighted breakdown and keep all sections accessible. | The browser can show other metrics without discarding the CLI preference. | +| Usage polling | Use Math.max(1, Number(opts.interval) || 2); --watch does not select terminal output with --web. | This retains the current CLI normalization and default string behavior. | +| Setup --refresh | Open /setup and start a protected catalog refresh on page load. | It retains the existing flag without adding a discovery job API. | +| Setup --json with --port | Reject before starting a web server. | --json selects the batch interface and --port selects the web interface. | +| Setup request size | Limit JSON bodies to 64 KiB. | The complete role selection is bounded and parseable before planning. | +| Setup HTTP status | Use 400 for malformed input, 422 for validation failures, and 500 for save failures. | Browser callers receive stable transport statuses while resultado.code retains CLI codes. | + +## Phase dependencies | Phase | Scope | Depends on | | --- | --- | --- | | P1 | Shared server, review compatibility, home page, and codedeck ui | None | -| P2 | Host, token, and Origin enforcement | P1 | +| P2 | Host, token, Origin, CSP, and server guard | P1 | | P3 | Pure setup planner and wizard reuse | P2 | -| P4 | Setup page, handlers, and command wiring | P3 | -| P5 | Usage page and handlers | P4 and merge of feat/orchestrator-usage | +| P4 | Setup page, routes, command wiring, and README update | P3 | +| P5 | Usage page, routes, command wiring, and query parity | P4 and merge of feat/orchestrator-usage into implementation HEAD | -The required CodeDeck command and page behavior is specified in spec.md. These artifacts stop at planning; implementation and implementation-time verifier reports are outside this docs-only task. +These artifacts stop at planning. They do not authorize source, test, plugin, or README changes during this documentation correction. diff --git a/.specs/features/web-console/spec.md b/.specs/features/web-console/spec.md index 3e40732..108e1c6 100644 --- a/.specs/features/web-console/spec.md +++ b/.specs/features/web-console/spec.md @@ -2,36 +2,39 @@ ## Problem Statement -Setup and usage analytics currently require the terminal, while review already runs a local page from the CLI. A browser console will make setup and usage easier to inspect while keeping the existing TUI and machine-readable command paths available with their current behavior. +Setup and usage analytics currently require the terminal, while review already serves a local page from the CLI. A browser console will make setup and usage easier to inspect while preserving the existing TUI and machine-readable command paths. ## Goals - [ ] Serve Review, Usage, and Setup from one local HTTP server started by a CLI command. - [ ] Keep the server bound to loopback and require token, Host, and Origin checks for actions that change state. -- [ ] Reuse one setup planning function from the existing wizard and the web setup page. -- [ ] Preserve existing setup batch, usage JSON, single-run usage, and statusline contracts. -- [ ] Render usage totals and each supported breakdown in the browser, refreshing the selected query every 2 seconds. +- [ ] Reuse a pure setup planner from the terminal wizard and the web setup flow without adding catalog validation to the planner. +- [ ] Preserve existing setup batch, usage JSON, single-run, backfill, observation, and statusline contracts. +- [ ] Render usage totals and all supported breakdowns in the browser, with filters and periodic refresh. +- [ ] Keep every page self-contained and test page behavior in Node without a DOM dependency. ## Current state (verified) -- Review currently owns its Node HTTP server, port parser, browser opener, and request handler in src/cli/commands/review.ts:1-135. The default port is 3100, the CLI accepts --port and --no-open, the server binds to 127.0.0.1, GET / and GET /review serve the same page, and GET /api/review loads a read-only review result. Existing route and command tests are in tests/review.test.ts:193-230 and tests/review-command.test.ts:5-27. -- src/web/review-page.ts:8-662 exports one self-contained HTML string. Its CSS starts at line 15 and its JavaScript at line 112. -- Usage query options and the CLI branches are in src/cli/commands/usage.ts:13-30 and :79-214. fetchUsageQuery at :37-77 calls daemon IPC method usage.query and falls back to a read-only SQLite database. The CLI supports date, repository, model, agent, grouping, TUI, watch, plain, and JSON options. A positional run ID uses the separate usage.get path at :103-125, which is used by the statusline contract tests. -- The current UsageQueryResult in src/daemon/protocol.ts:199-211 contains totals and byDay, byRepository, byModel, byAgent, and byRun arrays. It has no byOrigin field at this HEAD. The local feat/orchestrator-usage branch is not an ancestor of this HEAD, so P5 must follow that merge. The usage page must render byOrigin only when the merged result includes it. -- The setup wizard in src/cli/commands/setup.ts:625-801 discovers models, builds role/model and role-effort screens, then applies orchestrator, sandbox, autocompact, and profile selections before saving. The setup parser and batch options are at :804-990, SetupEnvelope is at :1000-1036, runSetupBatch is at :1363-1530, and the command entry is at :1560-1626. The current batch path applies role bindings; orchestrator, sandbox, and autocompact selections are wizard-only. -- Profile targeting in src/cli/commands/setup.ts:831-853 uses an explicit --profile target first, otherwise the active profile. An explicit target without a saved snapshot starts from the existing profile defaults; the wizard writes the selected snapshot at :774-779. -- Model discovery is announced before awaiting results in src/cli/commands/setup.ts:634-648. getCachedOrDiscoverModels in src/core/models.ts:151-190 can return the cache or discover and cache models. -- needsModelSetup is defined in src/cli/commands/setup.ts:437-452 and has no call site under src/. The wizard entry is executeSetupAction in src/cli/commands/setup.ts:1560-1598. +- Review owns its Node HTTP server, port parser, browser opener, and request handler in src/cli/commands/review.ts:1-135. It defaults to port 3100, accepts --port and --no-open, binds to 127.0.0.1, serves the same review page at GET / and GET /review, and serves read-only GET /api/review. Existing tests are in tests/review.test.ts:193-230 and tests/review-command.test.ts:5-27. +- src/web/review-page.ts:8-662 exports one self-contained HTML string with inline CSS and JavaScript. +- Usage option parsing and CLI branches are in src/cli/commands/usage.ts:13-30 and :79-214. fetchUsageQuery at :37-77 uses daemon IPC usage.query and falls back to read-only SQLite. The positional run-id path calls usage.get at :103-125. +- At this worktree HEAD, src/daemon/protocol.ts:199-211 has totals and byDay, byRepository, byModel, byAgent, and byRun. The feat/orchestrator-usage change adds required byOrigin to UsageQueryResult, --backfill, --observe, and --by origin. The web implementation must be based on a HEAD that contains that change. An older daemon reached over IPC may still return a result without byOrigin at runtime, so the page handles that field defensively. +- The setup command and wizard are in src/cli/commands/setup.ts:625-801 and :1560-1626. A non-batch invocation without both stdin and stdout TTYs exits with code 1 and prints “setup needs a terminal on both stdin and stdout”; it starts no discovery and writes no config. The behavior is covered by tests/setup-cli-contract.test.ts:315-334 and tests/setup-wizard.test.ts:1220-1243. +- The setup wizard allows typed harness:model values through src/cli/picker-state.ts:113-141 and keeps configured models that are absent from the catalog in src/cli/commands/setup.ts:499-518. It also preserves a role's binding and effort when their screens are skipped; first-run skips produce an empty agents object sentinel. Tests are in tests/setup-wizard.test.ts:579-592, :757-764, and :1179-1183. +- The setup command resolves explicit and active profiles in src/cli/commands/setup.ts:831-853. An active profile that does not exist raises SetupUsageError. An explicit profile without a saved snapshot starts from profile defaults with agents: {}. +- SetupEnvelope in src/cli/commands/setup.ts:1000-1036 uses top-level fields proposta, validacoes, mudancas, and resultado. runSetupBatch reads config without replacing invalid JSON and returns code 14 for invalid config or 15 for a read error at :1369-1387. It validates only winning --bind entries at :1404 and :1444-1459. loadConfig in src/config/config.ts:531-540 swallows read and parse errors, so web setup must use readConfigForSetup. +- getBatchModels in src/core/models.ts:276-389 supports allowNetwork:false for cached reads and refresh:true for network discovery, with a default 12,000 ms timeout. If discovery is incomplete or a requested harness reports an error, it discards partial network results and returns the cache fallback with status and discoveryError. +- Custom orchestrator parallelism accepts positive finite numbers in src/cli/commands/setup.ts:319-345 and :361-394. README.md:149-186 currently describes setup as a terminal picker. ## Out of Scope | Feature | Reason | | --- | --- | -| Removing or adding features to the setup TUI or usage TUI | Both remain frozen and reachable behind their flags. Removal is a later feature. | -| Moving HTTP into the daemon | The web server lives in the CLI process and only while its command runs. | -| Remote access or authentication beyond the per-start token | The server is local-only and binds to 127.0.0.1. | +| Removing or adding features to the setup TUI or usage TUI | Both remain frozen and reachable behind their flags. Their removal is a later feature. | +| Moving HTTP into the daemon | The server lives in the CLI process and only while its command runs. | +| Remote access or authentication beyond the per-start token | The server binds only to 127.0.0.1. | | Profile management beyond the existing --profile target | The web setup edits only the target already selected by the CLI. | -| Changing setup batch, usage JSON, or statusline contracts | Batch and agent callers keep their current interfaces and outputs. | +| Changing setup batch, usage JSON, usage.get, backfill, observe, or statusline contracts | Existing command and agent callers keep their current interfaces and outputs. | | New review page features | Review keeps its current page and GET API behavior. | | Build tooling, a frontend framework, or frontend dependencies | Pages remain self-contained HTML strings with inline CSS and JavaScript. | @@ -44,45 +47,57 @@ Setup and usage analytics currently require the terminal, while review already r | Home page entry command | codedeck ui | This is the requested default and leaves codedeck review at its current root page. | Yes | | Setup TUI entry | codedeck setup --tui | This is the requested default for keeping the frozen wizard reachable. | Yes | | Usage web entry | codedeck usage --web | This is the requested opt-in entry and leaves plain usage output unchanged. | Yes | -| Shared web port | 3100 by default; each web entry accepts --port | Review already uses 3100 and has a validated port option. | No | -| No-browser behavior | --no-open or a failed browser opener prints the full URL; the command keeps serving until SIGINT or SIGTERM | It matches the current review command and makes the URL usable in headless environments. | No | -| Host allowlist | Accept only 127.0.0.1: and localhost:, case-insensitively; reject missing or other Host values with HTTP 403 | These are the only browser hostnames supported by the loopback server. | No | -| Per-start token | Generate 32 cryptographically random bytes at server start and set them in a host-only HttpOnly; SameSite=Strict; Path=/ cookie | The browser sends the cookie for same-origin actions without exposing the token to page JavaScript. | No | -| Origin comparison | Require an HTTP Origin whose host and port match the accepted request Host on every POST route; return HTTP 403 when it is absent or different | This blocks cross-origin writes and gives every action route one testable rule. | No | -| Setup catalog refresh | GET /api/setup/catalog reads cached catalog state; POST /api/setup/catalog/refresh starts discovery | Discovery can write the model cache, so the explicit action uses the protected write path. | No | -| Setup --refresh behavior | Open /setup and request protected catalog refresh on page load | This preserves the existing flag while keeping discovery behind the action route. | No | -| Concurrent catalog refresh | Requests made while discovery is running share the same in-flight discovery | Model discovery can take seconds, and duplicate provider calls do not improve the displayed result. | No | -| Setup request size | Accept JSON bodies up to 64 KiB; return HTTP 400 for larger or malformed bodies | A full set of role selections is small; a fixed bound keeps local request parsing bounded. | No | -| Setup API status mapping | Malformed input returns 400, selection validation failures return 422 with a SetupEnvelope, and config save failures return 500 with saved=false | The page gets a predictable transport status while retaining the existing setup validation codes. | No | -| Usage refresh interval | Refresh the current query every 2 seconds; --interval overrides it when supplied with --web | Two seconds matches the current --watch default. | No | -| Usage query failure | Return HTTP 500 with a JSON error, show it in the page, and retain the last successful result | The user can still inspect the last known result while the next refresh retries. | No | -| Usage web with a run ID | A positional run ID or --run keeps the existing single-run branch, even if --web is also present | The statusline and single-run output path must keep their current contract. | No | -| --watch combined with --web | The browser page refreshes on its own; --interval controls its timer and --watch does not change the CLI output path | The web page replaces the need for terminal watch output. | No | -| --tui combined with setup batch flags | Reject the combination with the existing usage-error path | The batch path has a separate non-interactive contract and must not silently open either UI. | No | - -**Open questions:** none. The implementation choices without a human default are recorded above with their rationale. +| Non-TTY setup | Without batch flags, lack of either stdin or stdout TTY keeps the current code 1 and message; no server starts. | This is the orchestrator's confirmed decision and preserves current tests. | Yes | +| Shared web port | 3100 by default; web commands accept --port values from 1 through 65535. | Review already uses this default and range. Port 0 is available only through the injected server test seam. | No | +| No-browser behavior | --no-open or a failed browser opener prints the full initial URL, including ?t=, and keeps serving until SIGINT or SIGTERM. | The printed link must bootstrap the same per-start browser session as openBrowser. | No | +| Host allowlist | Accept only 127.0.0.1: and localhost:, case-insensitively; reject absent or other Host values with HTTP 403. | These are the only accepted browser hostnames for the loopback server. | No | +| Token transport | Generate 32 cryptographically random bytes per server start; carry the token as the t query parameter in the opened and printed initial URL. | The page receives the token out of band and does not embed it in HTML or JavaScript. | No | +| Session cookie | Set a host-only HttpOnly; SameSite=Strict; Path=/ cookie named codedeck_ui_token_ only after a valid token GET. | Browser cookies do not isolate by port, so the name prevents one local CodeDeck port from replacing another port's token. | No | +| Token bootstrap redirect | A valid token GET to an HTML route sets the cookie and returns HTTP 303 to the same path without t. | The token is removed from the address bar after bootstrap. | No | +| Origin comparison | Require an HTTP Origin whose host and port match the accepted request Host and bound port on every POST; return HTTP 403 if absent or different. | This gives every action route one same-origin rule. | No | +| Home links | Render a link only when its page route is registered in the active route table. | P1 must not advertise Usage before P5 registers it. | No | +| Setup catalog source | GET calls getBatchModels with allowNetwork:false; protected refresh calls it with refresh:true, allowNetwork:true, and timeoutMs:12000. | This is the single catalog source and matches the batch helper's fallback behavior. | No | +| Concurrent catalog refresh | Requests during one discovery share one in-flight refresh promise. | Duplicate provider requests do not improve the displayed result. | No | +| Setup --refresh behavior | On an interactive setup web launch, open /setup and request protected catalog refresh on page load. | This preserves the current flag while keeping discovery behind the action route. | No | +| Setup request size | Accept JSON bodies up to 64 KiB; return HTTP 400 for larger or malformed bodies. | A full role selection is small and a fixed limit bounds request parsing. | No | +| Setup API status mapping | Malformed input returns 400; binding validation failures return 422; save failures return 500. The SetupEnvelope records the existing setup code and saved=false. | The page receives a stable HTTP result and the existing validation codes remain visible. | No | +| setup --json with --port | Reject the combination through the setup usage-error path before starting a server. | --json selects the existing batch contract while --port selects web startup. | No | +| Usage --by with --web | Use --by as the initially selected breakdown; keep all available breakdown sections on the page. | This preserves the CLI grouping preference without hiding the other web sections. | No | +| Usage interval | Use a floor of 1 second and a numeric-conversion fallback of 2 seconds for web polling. | Commander supplies the default string "2", so the implementation cannot distinguish an omitted option from explicit --interval 2. | No | +| Aggregate usage --web --json | Start the browser page and print the token URL; --json affects aggregate output only when --web is absent. | The browser is the selected aggregate interface, while the positional run-id JSON contract remains unchanged. | No | +| Catalog refresh method | Use protected POST /api/setup/catalog/refresh with refresh:true, allowNetwork:true, and timeoutMs:12000. | Discovery may update the model cache, so it uses the guarded action path and the helper's current timeout. | No | +| Page behavior tests | Put state, rendering decisions, and polling transitions in TypeScript functions exported from each page module, then inject their source into its HTML string. | Node tests can exercise the same functions without installing a DOM implementation. | No | +| Post-merge usage result | Use the merged required byOrigin type, with a runtime fallback when an older daemon omits the field. | The feature branch adds byOrigin to the type; IPC can still reach an older daemon process. | Yes | +| Usage query failure | Return HTTP 500 with a JSON error, show the error in the page, and retain the last successful result. | A later poll can recover without removing the visible result. | No | +| Usage run ID with --web | When --backfill is absent, preserve the usage.get single-run branch, including --observe and --json, and start no web server when a positional ID or --run is present; --by origin remains aggregate-only. | The branch is used by the statusline and is evaluated before aggregate web startup. | No | +| Usage --backfill with --web | Run the existing backfill branch and start no web server. | The post-merge CLI checks --backfill before single-run and aggregate handling. | No | +| --watch with --web | The browser polls; --watch does not select terminal rendering. The normalized --interval value controls browser polling. | The web page replaces terminal watch output for this invocation. | No | +| Active profile missing | Surface the existing SetupUsageError and do not silently switch to a global target. | This preserves resolveSetupTarget behavior. | No | + +**Open questions:** none. Defaults without a human decision are recorded above with their rationale. ## User Stories ### P1: Shared local server and home page -**User Story**: As a CodeDeck user, I want one local browser entry point for review, setup, and usage so that I can use those screens without changing their command-line data paths. +**User Story**: As a CodeDeck user, I want one local browser entry point so that I can use registered Review, Usage, and Setup pages without changing their command-line data paths. **Why P1**: The shared server and route table are the base for every browser page. **Acceptance Criteria**: + + 1. WHEN a web entry command starts its server THEN the CLI SHALL bind only to 127.0.0.1. WEB-01 -2. WHILE codedeck ui, codedeck review, codedeck setup, or codedeck usage --web is running THEN the server SHALL stay alive until SIGINT or SIGTERM closes it. WEB-02 +2. WHILE a web command is running THEN SIGINT or SIGTERM SHALL close its server and invoke the injected exit function. WEB-02 3. WHEN a web entry command starts without --port THEN the server SHALL listen on port 3100. WEB-03 -4. IF --port is not an integer from 1 through 65535 THEN the command SHALL report the port error, exit with code 1, and skip server startup. WEB-04 +4. IF a user-supplied --port is not an integer from 1 through 65535 THEN the command SHALL report the port error, exit with code 1, and skip server startup. WEB-04 5. WHEN codedeck review serves GET / THEN the server SHALL return REVIEW_PAGE with HTTP 200 and text/html content type. WEB-05 6. WHEN codedeck review serves GET /review THEN the server SHALL return the same review page as GET /. WEB-06 7. WHEN the browser sends GET /api/review THEN the handler SHALL pass ref (default HEAD) and optional file to the existing review loader and return its result. WEB-07 -8. WHEN a user runs codedeck ui THEN GET / SHALL list links to /review, /usage, and /setup. WEB-08 -9. IF --no-open is set or the browser opener fails THEN the command SHALL print the full URL and keep the server running. WEB-09 -10. IF the requested port is already in use THEN the command SHALL print the listen error, exit with code 1, and not print a started-server URL. WEB-57 - -**Independent Test**: Start each command on an ephemeral test port, request its page and API routes, and assert the listen address, status, content type, and links. Stub browser opening to verify the printed URL and server lifetime. +8. WHEN the home page is rendered THEN it SHALL link every registered page route and no unregistered page route. WEB-08 +9. IF the requested port is already in use THEN the command SHALL print the listen error, exit with code 1, and not print a started-server URL. WEB-57 +10. WHEN the test seam listens on port 0 THEN the server SHALL use server.address().port when constructing its URL and security instance. WEB-75 +**Independent Test**: Start the server on an ephemeral test port, request each registered page and API route, and assert the address, status, content type, home links, and shutdown callbacks. ### P2: Local request security @@ -91,112 +106,145 @@ Setup and usage analytics currently require the terminal, while review already r **Why P2**: Setup apply and catalog refresh must reject forged requests before state changes occur. **Acceptance Criteria**: -1. IF a request Host is missing or differs from 127.0.0.1: and localhost: THEN the server SHALL return HTTP 403 before route dispatch. WEB-10 -2. WHEN the server starts THEN it SHALL generate a token from 32 cryptographically random bytes. WEB-11 -3. WHEN a POST route is requested THEN the server SHALL require the current token cookie and return HTTP 403 when it is missing, stale, or invalid. WEB-12 -4. WHEN a POST route is requested THEN the server SHALL require an Origin matching the request's HTTP host and bound port, and return HTTP 403 when Origin is absent or different. WEB-13 -5. IF a POST request carries a token cookie from a prior server process THEN the new server SHALL return HTTP 403. WEB-58 -**Independent Test**: Send accepted and rejected Host, token, and Origin combinations to protected and read routes. Confirm rejected action requests return 403 and do not call their handlers. + +1. IF Host is absent or matches neither 127.0.0.1: nor localhost: THEN the server SHALL return HTTP 403 before route dispatch. WEB-10 +2. WHEN the server starts THEN it SHALL generate one token from 32 cryptographically random bytes for that process. WEB-11 +3. WHEN an action POST is dispatched THEN the server SHALL require the host-only cookie named codedeck_ui_token_ and return HTTP 403 when its value is missing, stale, or invalid. WEB-12 +4. WHEN an action POST is dispatched THEN the server SHALL require an HTTP Origin matching the request Host and bound port and return HTTP 403 when Origin is absent or different. WEB-13 +5. WHEN an HTML GET carries the valid t query token THEN the server SHALL set the port-specific cookie and redirect to the same path without t. WEB-71 +6. IF an HTML GET lacks a valid t query token THEN the server SHALL NOT set the session cookie. WEB-72 +7. IF a page receives HTTP 403 for a protected action THEN its page logic SHALL show “This CodeDeck session has expired. Reload the page. If it still fails, restart the command and open its new URL.” WEB-73 +8. WHEN the server returns an HTML page THEN the response SHALL include Content-Security-Policy: frame-ancestors 'none'. WEB-74 +9. IF --no-open is set or the browser opener fails THEN the command SHALL print the full initial URL, including ?t=, and keep serving. WEB-09 +**Independent Test**: Send accepted and rejected Host, token, and Origin combinations to protected and read routes. Confirm rejected action requests return 403 without invoking handlers, bootstrap redirects strip t, and all HTML responses include the framing policy. ### P3: Shared setup planning -**User Story**: As a user of either setup interface, I want the same selections to produce the same config proposal and validation result so that the wizard and browser cannot drift. +**User Story**: As a user of either setup interface, I want the same selections to produce the same proposed config and diff so that the wizard and browser cannot drift in config assembly. **Why P3**: The browser can only match setup behavior after config planning is separated from terminal I/O. **Acceptance Criteria**: -1. WHEN the setup planner receives a current config, profile target, complete selections, and catalog validation data THEN it SHALL return a proposed config, diff, and validations without reading or writing files or starting discovery. WEB-14 -2. WHEN the selection contains role bindings THEN the planner SHALL apply each harness, model, and optional effort while preserving roles the selection leaves unchanged and unrelated config keys. WEB-15 -3. WHEN the selection contains an orchestrator mode THEN the planner SHALL put its parameter values in the proposed config without persisting a preset label. WEB-16 -4. WHEN the selection contains a sandbox value THEN the planner SHALL set defaultSandbox to workspace-write or danger-full-access as selected. WEB-17 -5. WHEN the selection turns autocompact on THEN the planner SHALL set autocompact.enabled to true and preserve other autocompact fields. WEB-18 -6. WHEN a profile target is supplied THEN the planner SHALL update only that profile snapshot; when no explicit target is supplied it SHALL use the active profile resolution already used by setup. WEB-19 -7. WHEN the planner returns a result THEN it SHALL expose the proposed config, diff, and validations in fields compatible with the current SetupEnvelope proposal. WEB-20 -8. IF a selected model binding is not accepted by setup validation THEN setup apply SHALL return the existing validation code and SHALL NOT save the proposed config. WEB-21 -9. WHEN the terminal wizard finishes a selection THEN it SHALL use the shared planner while all current setup-wizard and setup-cli-contract tests pass unchanged. WEB-22 -10. WHEN the selection turns autocompact off THEN the planner SHALL set enabled to false if the current config has an autocompact block and SHALL preserve the absent block otherwise. WEB-61 - -**Independent Test**: Call the planner with a global config and a profile config, assert exact proposed values, diff paths, and validation results, and run the existing setup wizard and CLI contract test files without modifying their current cases. + + +1. WHEN the setup planner receives a current RunAgentConfig, resolved target, and complete selections THEN it SHALL return a proposed config and diff without reading or writing files, discovering models, or validating against a catalog. WEB-14 +2. WHEN the selection contains a role binding THEN the planner SHALL set that role's harness, model, and optional effort to the selected values. WEB-15 +3. WHEN a role is omitted from selections THEN the planner SHALL preserve that role's current binding. WEB-86 +4. WHEN the planner updates selected config fields THEN it SHALL preserve unrelated config keys. WEB-87 +5. WHEN the selection contains an orchestrator mode THEN the planner SHALL put its parameter values in the proposed config without persisting a preset label. WEB-16 +6. WHEN the selection contains a sandbox value THEN the planner SHALL set defaultSandbox to workspace-write or danger-full-access as selected. WEB-17 +7. WHEN the selection turns autocompact on THEN the planner SHALL set autocompact.enabled to true and preserve other autocompact fields. WEB-18 +8. WHEN a setup response is assembled THEN it SHALL expose the exact top-level fields proposta, validacoes, mudancas, and resultado from SetupEnvelope. WEB-20 +9. WHEN setup receives an explicit --profile target THEN the planner SHALL update only that profile snapshot. WEB-19 +10. WHEN setup has no explicit --profile and an existing active profile THEN the planner SHALL use the resolved active profile snapshot. WEB-62 +11. WHEN the terminal wizard completes selections THEN it SHALL use the shared planner without adding catalog validation to the wizard path. WEB-22 +12. WHEN the selection turns autocompact off THEN the planner SHALL set enabled to false if the target config has an autocompact block and SHALL preserve the absent block otherwise. WEB-61 +**Independent Test**: Call the planner with global and profile RunAgentConfig values. Assert exact proposal and diff paths for each field, skipped role behavior, and absence of file, network, and catalog-validation calls. Run the existing setup wizard and CLI contract tests unchanged. ### P4: Browser setup -**User Story**: As a user configuring CodeDeck, I want to review a complete setup proposal in a browser before applying it so that I can see the config changes before they are saved. +**User Story**: As a user configuring CodeDeck, I want to review the current setup and a complete proposal in a browser before it is saved. -**Why P4**: This makes the browser the default setup interface while preserving the batch and TUI paths. +**Why P4**: This makes the browser the setup interface for interactive sessions while preserving the batch and TUI paths. **Acceptance Criteria**: + + 1. WHEN the browser requests GET /setup THEN the server SHALL return the self-contained setup page with HTTP 200. WEB-23 -2. WHEN the browser requests GET /api/setup/catalog THEN the handler SHALL return the cached model catalog and its fresh, offline, or unavailable status without starting discovery. WEB-24 -3. WHEN the user requests catalog refresh THEN the page SHALL show “Discovering models...” until the protected refresh response completes. WEB-25 -4. WHILE catalog discovery is in progress THEN concurrent refresh requests SHALL share the same discovery promise. WEB-26 -5. WHEN the browser posts valid selections to /api/setup/dry-run THEN the handler SHALL return a SetupEnvelope-like proposal, diff, and validations. WEB-27 -6. WHEN the browser posts selections to /api/setup/dry-run THEN the handler SHALL leave the config file unchanged. WEB-28 -7. WHEN validated selections with a non-empty diff are posted to /api/setup/apply THEN the handler SHALL save the proposed config and return resultado.status=applied with saved=true. WEB-29 -8. WHEN the proposal diff is empty THEN the apply handler SHALL return resultado.status=unchanged with saved=false and SHALL skip the config write. WEB-30 -9. IF a setup POST body is malformed, larger than 64 KiB, or has an invalid shape THEN the handler SHALL return HTTP 400 without saving config. WEB-31 -10. IF setup validation fails THEN the handler SHALL return HTTP 422 with the existing validation code and saved=false. WEB-32 -11. IF saving the config fails THEN the handler SHALL return HTTP 500 with saved=false and the config error message. WEB-33 -12. WHEN codedeck setup runs without batch flags or --tui THEN it SHALL start the web setup at /setup. WEB-34 -13. WHEN codedeck setup runs with --tui and no batch flags THEN it SHALL run the existing terminal wizard. WEB-35 -14. WHEN codedeck setup runs with existing batch flags THEN it SHALL keep the runSetupBatch JSON, dry-run, bind, exit-code, and save contracts unchanged. WEB-36 -15. WHEN the setup page is rendered THEN it SHALL offer harness and model choices for every role in ROLES. WEB-37 -16. WHEN a selected role supports a reasoning-effort screen THEN the setup page SHALL offer that role's effort choice and SHALL omit the effort control for opencode. WEB-38 -17. WHEN the setup page is rendered THEN it SHALL offer the orchestrator presets and investigate, selfWork, tools, and parallelism parameters. WEB-39 -18. WHEN the setup page is rendered THEN it SHALL offer workspace-write and danger-full-access for sandbox. WEB-40 -19. WHEN the setup page is rendered THEN it SHALL offer autocompact on and off. WEB-41 -20. WHEN setup starts with --profile THEN the page SHALL identify that target and apply its proposal only to that profile. WEB-42 -21. IF model discovery fails for one harness THEN the catalog response SHALL include that harness error with the other catalog results. WEB-59 - -**Independent Test**: Load the setup page, refresh the catalog, submit a dry-run, and assert its proposal and diff. Submit the same valid selection to apply and verify the selected global or profile config changes. Assert malformed, invalid, unchanged, and save-error paths do not claim a save. +2. WHEN the browser requests GET /api/setup/catalog THEN the handler SHALL call getBatchModels with allowNetwork:false and return models, status, source, ageMs, cacheWriteFailed, and discoveryError when present without starting discovery. WEB-24 +3. WHEN the browser requests GET /api/setup/state THEN the handler SHALL return whether the target is global or a named profile and the current bindings, per-role effort, orchestrator, sandbox, and autocompact values for page prefill. WEB-63 +4. IF the active profile does not exist THEN the state handler SHALL return the existing SetupUsageError message without selecting the global target. WEB-84 +5. WHEN the user requests catalog refresh THEN the setup page logic SHALL show “Discovering models...” until the refresh response completes. WEB-25 +6. WHILE catalog discovery is in progress THEN concurrent refresh requests SHALL share the same in-flight refresh promise. WEB-26 +7. WHEN the browser posts selections to /api/setup/dry-run THEN the handler SHALL return a JSON object with exact top-level fields proposta, validacoes, mudancas, and resultado. WEB-27 +8. WHEN the browser posts selections to /api/setup/dry-run THEN the handler SHALL leave the config file unchanged. WEB-28 +9. IF the proposal has a non-empty diff and all changed bindings validate THEN apply SHALL save it and return resultado.status=applied with saved=true. WEB-29 +10. IF the proposal diff is empty THEN apply SHALL return resultado.status=unchanged with saved=false and SHALL skip the config write. WEB-30 +11. IF a setup POST body is malformed, larger than 64 KiB, or has an invalid shape THEN the handler SHALL return HTTP 400 without saving config. WEB-31 +12. IF changed binding validation fails THEN the handler SHALL return HTTP 422, include the existing validation code in resultado.code, and set saved=false. WEB-32 +13. IF saving the config fails THEN the handler SHALL return HTTP 500 with saved=false and the config error message. WEB-33 +14. IF the config contains invalid JSON THEN web apply SHALL return resultado.code=14 and SHALL NOT write the config. WEB-76 +15. IF reading the config file fails THEN web apply SHALL return resultado.code=15 and SHALL NOT write the config. WEB-77 +16. WHEN codedeck setup runs with a TTY, no batch flags, and without --tui THEN it SHALL start web setup at /setup. WEB-34 +17. WHEN codedeck setup runs with --tui and a TTY THEN it SHALL run the existing terminal wizard. WEB-35 +18. WHEN codedeck setup runs with existing batch flags THEN it SHALL keep the runSetupBatch JSON, dry-run, bind, exit-code, validation, and save contracts unchanged, with winning --bind validation outside the pure planner. WEB-36 +19. WHEN the setup page is rendered THEN it SHALL offer harness and model choices for every role in ROLES. WEB-37 +20. WHEN the user enters harness:model text absent from the displayed catalog THEN the page SHALL allow that value in the selection and dry-run proposal. WEB-64 +21. IF an existing binding's harness and model are unchanged from the resolved target THEN web apply SHALL preserve it without catalog validation, even when it is off-catalog. WEB-85 +22. WHEN a binding's harness or model differs from the resolved target THEN web apply SHALL validate that binding and SHALL NOT validate bindings changed only by effort. WEB-21 +23. WHEN a role is skipped THEN setup SHALL keep its target binding unchanged or leave it unset when no binding exists. WEB-65 +24. WHEN all first-run role screens are skipped and no bindings exist THEN apply SHALL write the empty agents object sentinel. WEB-66 +25. WHEN the user skips an effort screen for an unchanged harness and model THEN the page logic SHALL preserve that binding's current effort. WEB-67 +26. WHEN the selected orchestrator mode is custom and the user enters positive finite parallelism N THEN the planner SHALL store N as the numeric parallelism value. WEB-68 +27. WHEN a selected role supports a reasoning-effort screen THEN the setup page SHALL offer that role's effort values and SHALL omit the effort control for opencode. WEB-38 +28. WHEN the setup page is rendered THEN it SHALL offer the orchestrator presets and investigate, selfWork, tools, and parallelism parameters. WEB-39 +29. WHEN the setup page is rendered THEN it SHALL offer workspace-write and danger-full-access for sandbox. WEB-40 +30. WHEN the setup page is rendered THEN it SHALL offer autocompact on and off. WEB-41 +31. WHEN setup starts with --profile THEN the state response SHALL identify that profile and apply its proposal only to that profile. WEB-42 +32. IF model discovery is incomplete or any requested harness returns an error THEN the refresh response SHALL return getBatchModels cache fallback and discoveryError without partial network results. WEB-59 +33. IF codedeck setup runs without batch flags and either stdin or stdout is not a TTY THEN it SHALL exit with code 1, print “setup needs a terminal on both stdin and stdout”, and start no server. WEB-69 +34. IF codedeck setup receives both --json and --port THEN it SHALL report a setup usage error and start no server. WEB-70 +35. WHEN setup guidance is updated THEN README.md lines 154 and 186 SHALL describe browser setup as the default, --tui as the picker entry, and --refresh as the catalog refresh option. WEB-82 +**Independent Test**: Request setup state and catalog, refresh, submit dry-run and apply, and assert exact envelope fields and writes. Cover changed and unchanged off-catalog bindings, profile targets, all role and effort skips, typed models, custom numeric parallelism, invalid config codes, and non-TTY command behavior. ### P5: Browser usage analytics -**User Story**: As a user reviewing agent costs, I want a browser dashboard with the same filters as the CLI and all available breakdowns so that I can inspect usage without a full-screen terminal. +**User Story**: As a user reviewing agent costs, I want a browser dashboard with the CLI filters and available breakdowns so that I can inspect usage without the terminal. -**Why P5**: This depends on the pending usage-origin data change and keeps the existing usage TUI and command outputs intact. +**Why P5**: The usage web must build on the merged orchestrator-usage contracts and retain compatibility with older daemons. **Acceptance Criteria**: + + 1. WHEN the browser requests GET /usage THEN the server SHALL return the self-contained usage page with HTTP 200. WEB-43 -2. WHEN GET /api/usage receives filters THEN it SHALL map them to the CLI's UsageQueryParams, including all before today before days precedence, the 3d, 7d, and 30d period mappings, other positive days as local-midnight since, and current overriding repo. WEB-44 -3. WHEN a usage query succeeds THEN the page SHALL display every field in UsageTotals. WEB-45 -4. WHEN a usage query succeeds THEN the page SHALL display the byDay buckets. WEB-46 -5. WHEN a usage query succeeds THEN the page SHALL display the byRepository buckets. WEB-47 -6. WHEN a usage query succeeds THEN the page SHALL display the byModel buckets. WEB-48 -7. WHEN a usage query succeeds THEN the page SHALL display the byAgent buckets. WEB-49 -8. WHEN a usage query succeeds THEN the page SHALL display the byRun buckets. WEB-50 -9. WHERE the merged UsageQueryResult contains byOrigin THEN the page SHALL display the byOrigin buckets. WEB-51 -10. IF UsageQueryResult has no byOrigin field THEN the page SHALL render the other usage sections without an error. WEB-52 -11. WHILE the usage page is open THEN it SHALL refresh the current filters every 2 seconds unless --interval supplied a different interval. WEB-53 -12. WHEN codedeck usage runs with --web and without a run ID THEN it SHALL open /usage with the supplied aggregate filters. WEB-54 -13. WHEN codedeck usage runs without --web THEN it SHALL keep the current snapshot, TUI, watch, plain, and JSON paths unchanged. WEB-55 -14. WHEN codedeck usage receives a positional run ID or --run THEN it SHALL keep the existing usage.get single-run result and statusline contract, including when --web is also present. WEB-56 -15. IF a usage query fails after the page has rendered a result THEN the page SHALL show the query error and retain the last successful result. WEB-60 - -**Independent Test**: Query a fixture result with each current grouping array and optional byOrigin. Assert filter mapping and page output with byOrigin present and absent, then verify the browser refresh uses the same filter values every 2 seconds. +2. WHEN the CLI or web handler calls buildUsageQueryParams(opts, cwd, now) THEN it SHALL apply the CLI precedence of all, today, days, then default today when since is absent; map 3, 7, and 30 to named periods and other positive day counts to local-midnight since; let current override repo; and pass through since, until, model, and agent so equal inputs produce equal UsageQueryParams. WEB-44 +3. WHEN a usage query succeeds THEN the page logic SHALL expose every UsageTotals field for rendering. WEB-45 +4. WHEN a usage query succeeds THEN the page logic SHALL expose byDay buckets for rendering. WEB-46 +5. WHEN a usage query succeeds THEN the page logic SHALL expose byRepository buckets for rendering. WEB-47 +6. WHEN a usage query succeeds THEN the page logic SHALL expose byModel buckets for rendering. WEB-48 +7. WHEN a usage query succeeds THEN the page logic SHALL expose byAgent buckets for rendering. WEB-49 +8. WHEN a usage query succeeds THEN the page logic SHALL expose byRun buckets for rendering. WEB-50 +9. WHEN a post-merge result contains byOrigin THEN the page logic SHALL expose its buckets for rendering. WEB-51 +10. IF a result returned by an older daemon has no byOrigin field THEN the page logic SHALL render the other breakdowns without an error. WEB-52 +11. WHILE the usage page is open THEN its state logic SHALL poll the active query using the normalized interval value. WEB-53 +12. WHEN codedeck usage runs with --web and no run ID THEN it SHALL open /usage with aggregate filters and the selected --by breakdown. WEB-54 +13. WHEN codedeck usage runs without --web THEN it SHALL preserve the snapshot, TUI, watch, plain, JSON, --by origin, --observe, and --backfill contracts from the CLI and feat/orchestrator-usage. WEB-55 +14. IF --backfill is absent and codedeck usage receives a positional run ID or --run THEN it SHALL call usage.get, preserve valid --observe data and --json output, ignore aggregate-only --by origin, and start no web server even if --web is present. WEB-56 +15. IF codedeck usage receives --backfill together with --web THEN it SHALL run backfill and start no web server. WEB-83 +16. WHEN usage web polling normalizes --interval with Math.max(1, Number(opts.interval) || 2) THEN zero and non-numeric values SHALL resolve to 2 seconds and negative or positive values below 1 SHALL resolve to 1 second. WEB-81 +17. WHEN a usage page filter for period, repo, model, agent, since, or until changes THEN the page logic SHALL issue a new query with the updated filters. WEB-78 +18. IF a usage query fails after a successful result THEN the page logic SHALL show the error and retain the last successful result. WEB-60 +19. WHEN codedeck usage receives --by origin THEN the page logic SHALL select origin as the initial breakdown while leaving all available sections reachable. WEB-80 +**Independent Test**: Use fixed options, cwd, and time to assert CLI and web query parameter parity. Test the page logic directly in Node for filters, polling, query errors, and results with and without byOrigin. Test CLI runs with --web, --by origin, --backfill, --observe, --json, and a single-run ID without starting an unintended server. ## Edge Cases -- IF the requested port is invalid or already in use THEN the command SHALL print the listen error and exit without claiming the server started. -- IF Host, Origin, or the token cookie fails validation THEN the route handler SHALL not run. -- IF model discovery fails for one harness THEN the catalog response SHALL keep that harness error beside other returned catalog entries. -- IF config validation rejects one selected binding THEN apply SHALL save none of the proposal. -- IF the usage query fails after the initial render THEN the page SHALL show the error and retain the last successful usage result. -- IF the server process exits THEN its per-start token SHALL no longer authorize requests. +- IF Host is absent or matches neither accepted loopback Host value THEN no route handler SHALL run. +- IF an HTML GET lacks a valid token query parameter THEN the response SHALL NOT set the session cookie. +- IF a protected POST returns 403 THEN the page SHALL show the session-expired reload/restart message. +- IF a user port is invalid or already in use THEN the command SHALL exit without claiming the server started. +- IF a model refresh returns partial network results THEN the page SHALL show only the getBatchModels result and its status, not a partial merged network catalog. +- IF a changed binding fails catalog validation THEN apply SHALL save none of the proposal. +- IF config reading returns invalid JSON or an I/O error THEN web apply SHALL return code 14 or 15 respectively and SHALL NOT write. +- IF an older daemon omits byOrigin THEN the page SHALL render totals and every other breakdown. +- IF codedeck setup lacks either TTY stream without batch flags THEN it SHALL preserve the current exit and message and SHALL NOT start the web server. ## Requirement Traceability +Each acceptance criterion has one requirement ID and maps to the task that implements and tests it. + | Requirement ID | Story | Phase | Status | Task | | --- | --- | --- | --- | --- | | WEB-01 | P1: Shared local server and home page | P1 | In Tasks | T1 | -| WEB-02 | P1: Shared local server and home page | P1 | In Tasks | T1 | +| WEB-02 | P1: Shared local server and home page | P1 | In Tasks | T1, T7 | | WEB-03 | P1: Shared local server and home page | P1 | In Tasks | T1 | | WEB-04 | P1: Shared local server and home page | P1 | In Tasks | T1 | | WEB-05 | P1: Shared local server and home page | P1 | In Tasks | T3 | | WEB-06 | P1: Shared local server and home page | P1 | In Tasks | T3 | | WEB-07 | P1: Shared local server and home page | P1 | In Tasks | T3 | -| WEB-08 | P1: Shared local server and home page | P1 | In Tasks | T2, T4, T5 | -| WEB-09 | P1: Shared local server and home page | P1 | In Tasks | T1, T4 | +| WEB-08 | P1: Shared local server and home page | P1 | In Tasks | T2, T4, T5, T13, T18 | +| WEB-09 | P2: Local request security | P2 | In Tasks | T7, T12, T17 | | WEB-10 | P2: Local request security | P2 | In Tasks | T6, T7 | | WEB-11 | P2: Local request security | P2 | In Tasks | T6 | | WEB-12 | P2: Local request security | P2 | In Tasks | T6, T7 | @@ -207,12 +255,12 @@ Setup and usage analytics currently require the terminal, while review already r | WEB-17 | P3: Shared setup planning | P3 | In Tasks | T8 | | WEB-18 | P3: Shared setup planning | P3 | In Tasks | T8 | | WEB-19 | P3: Shared setup planning | P3 | In Tasks | T8 | -| WEB-20 | P3: Shared setup planning | P3 | In Tasks | T8 | -| WEB-21 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-20 | P3: Shared setup planning | P3 | In Tasks | T11 | +| WEB-21 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-22 | P3: Shared setup planning | P3 | In Tasks | T9 | | WEB-23 | P4: Browser setup | P4 | In Tasks | T10 | | WEB-24 | P4: Browser setup | P4 | In Tasks | T11 | -| WEB-25 | P4: Browser setup | P4 | In Tasks | T10, T11 | +| WEB-25 | P4: Browser setup | P4 | In Tasks | T10 | | WEB-26 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-27 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-28 | P4: Browser setup | P4 | In Tasks | T11 | @@ -223,39 +271,68 @@ Setup and usage analytics currently require the terminal, while review already r | WEB-33 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-34 | P4: Browser setup | P4 | In Tasks | T12 | | WEB-35 | P4: Browser setup | P4 | In Tasks | T12 | -| WEB-36 | P4: Browser setup | P4 | In Tasks | T12 | +| WEB-36 | P4: Browser setup | P4 | In Tasks | T9, T12 | | WEB-37 | P4: Browser setup | P4 | In Tasks | T10 | | WEB-38 | P4: Browser setup | P4 | In Tasks | T10 | | WEB-39 | P4: Browser setup | P4 | In Tasks | T10 | | WEB-40 | P4: Browser setup | P4 | In Tasks | T10 | | WEB-41 | P4: Browser setup | P4 | In Tasks | T10 | -| WEB-42 | P4: Browser setup | P4 | In Tasks | T10, T12 | -| WEB-43 | P5: Browser usage analytics | P5 | In Tasks | T15 | -| WEB-44 | P5: Browser usage analytics | P5 | In Tasks | T14 | -| WEB-45 | P5: Browser usage analytics | P5 | In Tasks | T15 | -| WEB-46 | P5: Browser usage analytics | P5 | In Tasks | T15 | -| WEB-47 | P5: Browser usage analytics | P5 | In Tasks | T15 | -| WEB-48 | P5: Browser usage analytics | P5 | In Tasks | T15 | -| WEB-49 | P5: Browser usage analytics | P5 | In Tasks | T15 | -| WEB-50 | P5: Browser usage analytics | P5 | In Tasks | T15 | -| WEB-51 | P5: Browser usage analytics | P5 | In Tasks | T15 | -| WEB-52 | P5: Browser usage analytics | P5 | In Tasks | T15 | -| WEB-53 | P5: Browser usage analytics | P5 | In Tasks | T15 | -| WEB-54 | P5: Browser usage analytics | P5 | In Tasks | T16 | -| WEB-55 | P5: Browser usage analytics | P5 | In Tasks | T16 | -| WEB-56 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-42 | P4: Browser setup | P4 | In Tasks | T12, T13 | +| WEB-43 | P5: Browser usage analytics | P5 | In Tasks | T16, T18 | +| WEB-44 | P5: Browser usage analytics | P5 | In Tasks | T14, T15 | +| WEB-45 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-46 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-47 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-48 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-49 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-50 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-51 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-52 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-53 | P5: Browser usage analytics | P5 | In Tasks | T16 | +| WEB-54 | P5: Browser usage analytics | P5 | In Tasks | T17 | +| WEB-55 | P5: Browser usage analytics | P5 | In Tasks | T17 | +| WEB-56 | P5: Browser usage analytics | P5 | In Tasks | T17 | | WEB-57 | P1: Shared local server and home page | P1 | In Tasks | T1 | -| WEB-58 | P2: Local request security | P2 | In Tasks | T6, T7 | | WEB-59 | P4: Browser setup | P4 | In Tasks | T11 | -| WEB-60 | P5: Browser usage analytics | P5 | In Tasks | T15 | +| WEB-60 | P5: Browser usage analytics | P5 | In Tasks | T16 | | WEB-61 | P3: Shared setup planning | P3 | In Tasks | T8 | - -**Coverage**: 61 total requirements, 61 mapped to tasks, 0 unmapped. +| WEB-62 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-63 | P4: Browser setup | P4 | In Tasks | T10, T11 | +| WEB-64 | P4: Browser setup | P4 | In Tasks | T10 | +| WEB-65 | P4: Browser setup | P4 | In Tasks | T10, T11 | +| WEB-66 | P4: Browser setup | P4 | In Tasks | T8, T10, T11 | +| WEB-67 | P4: Browser setup | P4 | In Tasks | T10 | +| WEB-68 | P4: Browser setup | P4 | In Tasks | T8, T10 | +| WEB-69 | P4: Browser setup | P4 | In Tasks | T12 | +| WEB-70 | P4: Browser setup | P4 | In Tasks | T12 | +| WEB-71 | P2: Local request security | P2 | In Tasks | T6, T7 | +| WEB-72 | P2: Local request security | P2 | In Tasks | T6, T7 | +| WEB-73 | P2: Local request security | P2 | In Tasks | T7, T10 | +| WEB-74 | P2: Local request security | P2 | In Tasks | T6, T7 | +| WEB-75 | P1: Shared local server and home page | P1 | In Tasks | T1, T7 | +| WEB-76 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-77 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-78 | P5: Browser usage analytics | P5 | In Tasks | T15, T16 | +| WEB-80 | P5: Browser usage analytics | P5 | In Tasks | T16, T17 | +| WEB-81 | P5: Browser usage analytics | P5 | In Tasks | T16, T17 | +| WEB-82 | P4: Browser setup | P4 | In Tasks | T19 | +| WEB-83 | P5: Browser usage analytics | P5 | In Tasks | T17 | +| WEB-84 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-85 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-86 | P3: Shared setup planning | P3 | In Tasks | T8 | +| WEB-87 | P3: Shared setup planning | P3 | In Tasks | T8 | + +**Coverage**: 85 total requirements, 85 mapped to tasks, 0 unmapped. +## External Dependencies + +None. The feature adds no external-system integration; catalog access reuses the repository's existing model-discovery code. ## Success Criteria -- [ ] codedeck ui lists Review, Usage, and Setup, and each linked page returns HTTP 200. +- [ ] codedeck ui lists only pages registered in its route table, and each linked route returns HTTP 200. - [ ] Invalid Host, Origin, or token requests return HTTP 403 before an action handler runs. -- [ ] Setup dry-run returns a proposal and never writes config; apply writes only validated changes. -- [ ] Existing tests for setup wizard, setup CLI contract, review, usage JSON, and statusline behavior pass without changing their current cases. -- [ ] After feat/orchestrator-usage merges, the usage page renders all available breakdowns and refreshes the active query every 2 seconds by default. +- [ ] A valid token URL bootstraps a port-specific cookie and redirects to a URL without t. +- [ ] Setup state pre-fills the resolved profile or global target, and dry-run never writes config. +- [ ] Web apply validates only changed harness:model bindings and saves no invalid proposal. +- [ ] Existing setup wizard, setup CLI contract, review, usage CLI, JSON, and statusline tests pass unchanged. +- [ ] After the orchestrator-usage commit is an ancestor of implementation HEAD, usage query params match between CLI and web and page logic passes Node tests without a DOM dependency. diff --git a/.specs/features/web-console/tasks.md b/.specs/features/web-console/tasks.md index d34e57e..2ce3f86 100644 --- a/.specs/features/web-console/tasks.md +++ b/.specs/features/web-console/tasks.md @@ -1,10 +1,8 @@ # Web console tasks -## Execution Protocol (mandatory) +## Execution protocol -Implement these tasks with the tlc-spec-driven skill. Follow its Execute flow, per-task gates, atomic commits, and final verifier. The implementation stays within the source and test files named by each task. Do not change plugin files, add a frontend framework, or run the full Vitest suite. - ---- +Implement these tasks with the tlc-spec-driven skill. Keep tests in the task that changes the code they cover. Run only the scoped commands below. Do not change plugin files or add a frontend framework. Do not run the full Vitest suite. **Design**: .specs/features/web-console/design.md @@ -12,407 +10,499 @@ Implement these tasks with the tlc-spec-driven skill. Follow its Execute flow, p ## Test Coverage Matrix -> Generated from the current Vitest setup, repository instructions, and spec. Tests run under Node and live in tests/. The current project instructions require scoped Vitest commands and prohibit the full suite. +> Generated from the feature requirements, repository test locations, and the project instruction to use scoped Vitest commands. The Node test environment has no DOM dependency. -| Code Layer | Required Test Type | Coverage Expectation | Location Pattern | Run Command | +| Code layer | Required test type | Coverage expectation | Location pattern | Run command | | --- | --- | --- | --- | --- | -| Web server/router | Integration | Loopback binding, port parsing, route dispatch, review aliases, browser-open fallback, and close behavior | tests/web-server.test.ts, tests/review.test.ts, tests/review-command.test.ts | npx vitest run tests/web-server.test.ts tests/review.test.ts tests/review-command.test.ts | -| Security | Integration | Accepted and rejected Host, token, and Origin combinations; rejected requests never invoke handlers | tests/web-security.test.ts, tests/web-server.test.ts | npx vitest run tests/web-security.test.ts tests/web-server.test.ts | -| Setup core | Unit and integration | Pure planner results for each setup field, profile targeting, diff and validation results, wizard compatibility, and existing setup contracts | tests/setup-plan.test.ts, tests/setup-wizard.test.ts, tests/setup-cli-contract.test.ts | npx vitest run tests/setup-plan.test.ts tests/setup-wizard.test.ts tests/setup-cli-contract.test.ts | -| Setup web handlers | Integration | Catalog reads and refresh, dry-run without writes, apply, unchanged apply, malformed body, validation failure, and save failure | tests/setup-web.test.ts | npx vitest run tests/setup-web.test.ts | -| Usage web handler | Integration | Every supported filter mapping, query success, and IPC plus SQLite error handling | tests/usage-web.test.ts | npx vitest run tests/usage-web.test.ts | -| HTML pages | Unit | Home links, setup controls, visible discovery state, usage totals and breakdowns, optional byOrigin, and refresh interval | tests/web-pages.test.ts | npx vitest run tests/web-pages.test.ts | -| CLI wiring | Command contract | ui, review, setup, and usage flags; setup batch output; usage JSON and single-run statusline behavior | tests/web-cli.test.ts, tests/review-command.test.ts, tests/setup-cli-contract.test.ts, tests/usage-statusline-contract.test.ts | npx vitest run tests/web-cli.test.ts tests/review-command.test.ts tests/setup-cli-contract.test.ts tests/usage-statusline-contract.test.ts | +| Web server/router | Integration | Loopback bind, actual port after listen, port parsing, route dispatch, review aliases, browser URL, listen failure, and injected shutdown | tests/web-server.test.ts, tests/review.test.ts, tests/review-command.test.ts | npx vitest run tests/web-server.test.ts tests/review.test.ts tests/review-command.test.ts | +| Security | Integration | Allowed and rejected Host, token bootstrap and cookie, Origin, POST rejection before dispatch, and HTML framing header | tests/web-security.test.ts, tests/web-server.test.ts | npx vitest run tests/web-security.test.ts tests/web-server.test.ts | +| Setup core | Unit and integration | Planner fields and preservation, profile targets, skip behavior, no catalog validation in planner, separate changed-binding validation, wizard and batch compatibility | tests/setup-plan.test.ts, tests/setup-wizard.test.ts, tests/setup-cli-contract.test.ts | npx vitest run tests/setup-plan.test.ts tests/setup-wizard.test.ts tests/setup-cli-contract.test.ts | +| Setup web handlers | Integration | State prefill, catalog cache and refresh fallback, dry-run no-write, apply, changed-only validation, invalid config codes, malformed body, and save errors | tests/setup-web.test.ts | npx vitest run tests/setup-web.test.ts | +| Usage query builder | Unit and command contract | CLI and web callers produce identical UsageQueryParams for the same options, cwd, and clock; all supported filters and precedence | tests/usage-cli.test.ts | npx vitest run tests/usage-cli.test.ts | +| Usage web handler | Integration | Query mapping, every supported filter, query success, and query error response | tests/usage-web.test.ts | npx vitest run tests/usage-web.test.ts | +| HTML pages and page behavior | Unit | Direct Node tests of injected page functions for setup state, free-text input, discovery, 403 message, filters, polling, rendering data, optional byOrigin, and retained result on error | tests/web-pages.test.ts | npx vitest run tests/web-pages.test.ts | +| CLI wiring | Command contract | ui, review, setup, and usage routes and flags; setup batch and non-TTY behavior; usage.get with --web --json without server startup | tests/web-cli.test.ts, tests/review-command.test.ts, tests/setup-cli-contract.test.ts, tests/usage-cli.test.ts, tests/usage-statusline-contract.test.ts | npx vitest run tests/web-cli.test.ts tests/review-command.test.ts tests/setup-cli-contract.test.ts tests/usage-cli.test.ts tests/usage-statusline-contract.test.ts | +| Documentation | none | Text-only README update; no test coverage required | None | git diff --check -- README.md | ## Gate Check Commands -| Gate Level | When to Use | Command | +| Gate level | When to use | Command | | --- | --- | --- | | Task | After each task | Run the scoped command in that task's Gate field. | | P1 | After shared server and home wiring | npx vitest run tests/web-server.test.ts tests/web-pages.test.ts tests/review.test.ts tests/review-command.test.ts tests/web-cli.test.ts | -| P2 | After security integration | npx vitest run tests/web-security.test.ts tests/web-server.test.ts | +| P2 | After security integration | npx vitest run tests/web-security.test.ts tests/web-server.test.ts tests/web-pages.test.ts | | P3 | After setup core extraction | npx vitest run tests/setup-plan.test.ts tests/setup-wizard.test.ts tests/setup-cli-contract.test.ts | | P4 | After setup web wiring | npx vitest run tests/setup-web.test.ts tests/web-pages.test.ts tests/web-cli.test.ts tests/setup-cli-contract.test.ts | -| P5 | After usage web wiring and feat/orchestrator-usage merge | npx vitest run tests/usage-web.test.ts tests/web-pages.test.ts tests/web-cli.test.ts tests/usage-statusline-contract.test.ts | +| P5 | After the usage changes land | git merge-base --is-ancestor origin/feat/orchestrator-usage HEAD && npx vitest run tests/usage-cli.test.ts tests/usage-web.test.ts tests/web-pages.test.ts tests/web-cli.test.ts tests/usage-statusline-contract.test.ts | ## Execution Plan -Phases run in P1 to P5 order. Tasks run sequentially within each phase. P5 must not start until feat/orchestrator-usage has merged into the implementation base. +Phases run in dependency order. Within each phase, start a task after its listed dependencies pass. P5 cannot start until P4 is complete and origin/feat/orchestrator-usage is an ancestor of implementation HEAD. ### Phase 1: Shared server and home page ```text T1 -> T2 T1 -> T3 -T1 -> T4 T2 -> T4 T3 -> T4 T4 -> T5 ``` -### Phase 2: Security +### Phase 2: Request security ```text -T1 -> T6 -> T7 +T1 -> T6 T1 -> T7 +T6 -> T7 ``` -### Phase 3: Shared setup planner +### Phase 3: Shared setup planning ```text +T7 -> T8 T8 -> T9 ``` -### Phase 4: Setup web +### Phase 4: Browser setup ```text T8 -> T10 -T9 -> T10 T7 -> T11 T8 -> T11 T9 -> T12 T10 -> T12 T11 -> T12 T12 -> T13 +T12 -> T19 ``` -### Phase 5: Usage web +### Phase 5: Browser usage ```text -T7 -> T14 -T13 -> T14 -> T15 -> T16 -> T17 -T14 -> T16 -T13 -> T17 +T14 -> T15 +T7 -> T15 +T15 -> T16 +T14 -> T17 +T15 -> T17 +T16 -> T17 +T13 -> T18 +T15 -> T18 +T16 -> T18 ``` ## Task Breakdown ### T1: Extract the shared server and router -**What**: Add the loopback server, route table, port parser, browser opener, URL output, and signal shutdown. +**What**: Move loopback server startup, route dispatch, port parsing, browser opening, and testable shutdown into the shared web server. **Where**: src/web/server.ts **Depends on**: None **Reuses**: src/cli/commands/review.ts -**Requirement**: WEB-01, WEB-02, WEB-03, WEB-04, WEB-09, WEB-57 -**Tools**: MCP none; Skill tlc-spec-driven +**Requirement**: WEB-01, WEB-02, WEB-03, WEB-04, WEB-57, WEB-75 **Done when**: -- The server binds to 127.0.0.1 and accepts a validated port from 1 through 65535. -- The default port is 3100. -- A failed browser open or --no-open prints the full URL while the process keeps serving. -- SIGINT and SIGTERM close the server. -- An occupied port prints a listen error and exits with code 1 before reporting a started URL. -- Route tests cover successful dispatch, unknown paths, listen errors, and close behavior. +- The server binds to 127.0.0.1 and defaults to port 3100. +- The user-facing parser accepts only integer ports from 1 through 65535; an injected test seam can listen on port 0. +- After listen, the server reads server.address().port and uses that actual value for its returned address. +- Signal handling calls the injected close function and then the injected exit function for SIGINT and SIGTERM. +- A listen failure reports the error and exits with code 1 without printing a started URL. +- Server tests cover route dispatch, unknown routes, ephemeral bound port, listen failure, and both shutdown callbacks. **Tests**: Integration, tests/web-server.test.ts **Gate**: npx vitest run tests/web-server.test.ts -### T2: Add the home page +### T2: Render the home page from registered routes -**What**: Add a self-contained home page with links to Review, Usage, and Setup. +**What**: Add a self-contained home page renderer that receives the registered page routes and creates links only for those routes. **Where**: src/web/home-page.ts **Depends on**: T1 **Reuses**: src/web/review-page.ts **Requirement**: WEB-08 -**Tools**: MCP none; Skill tlc-spec-driven **Done when**: -- The page contains links to /review, /usage, and /setup. -- The page has no external CSS, JavaScript, image, or font dependency. -- Page tests assert each link target. +- The home renderer accepts route labels and paths instead of hard-coding future routes. +- Tests pass only /review as registered and assert there is no /usage or /setup link. +- The page uses inline CSS and has no external asset dependency. **Tests**: Unit, tests/web-pages.test.ts **Gate**: npx vitest run tests/web-pages.test.ts ### T3: Rewire the review command -**What**: Move review server startup to the shared server while preserving the handler exports and current review routes. +**What**: Start review with the shared server and preserve its page and API behavior. **Where**: src/cli/commands/review.ts **Depends on**: T1 **Reuses**: src/web/review-page.ts and src/git/review.ts **Requirement**: WEB-05, WEB-06, WEB-07 -**Tools**: MCP none; Skill tlc-spec-driven **Done when**: -- GET / and GET /review still serve the current review page. +- GET / and GET /review return the current review page with HTTP 200. - GET /api/review keeps ref=HEAD as its default and passes file when supplied. -- Existing review command options and exported test seams remain available. -- Existing review route and command tests pass without changing their current assertions. +- Existing review options and test seams remain available. +- Existing review assertions pass unchanged. **Tests**: Integration, tests/review.test.ts and tests/review-command.test.ts **Gate**: npx vitest run tests/review.test.ts tests/review-command.test.ts ### T4: Add the ui command -**What**: Start the shared server with the home page as root and the review route available. +**What**: Start the shared server with the home page at / and the registered review page. **Where**: src/cli/commands/ui.ts -**Depends on**: T1, T2, T3 -**Reuses**: Shared server and review handler -**Requirement**: WEB-08, WEB-09 -**Tools**: MCP none; Skill tlc-spec-driven +**Depends on**: T2, T3 +**Reuses**: Shared server, home renderer, and review handler +**Requirement**: WEB-08 **Done when**: - codedeck ui opens / by default. -- The route table serves /review. +- Its initial page route table registers the home page and /review. - --port and --no-open use the shared server options. -- Command tests assert the home path, flags, printed URL, and browser-open failure behavior. +- Command tests assert registered links, route status, and URL output. **Tests**: Command contract, tests/web-cli.test.ts **Gate**: npx vitest run tests/web-cli.test.ts ### T5: Register ui with the CLI -**What**: Register the ui command with the root Commander program. +**What**: Register codedeck ui with the root Commander program. **Where**: src/cli/index.ts **Depends on**: T4 -**Reuses**: Existing command registration order in src/cli/index.ts +**Reuses**: Existing command registration **Requirement**: WEB-08 -**Tools**: MCP none; Skill tlc-spec-driven **Done when**: - codedeck --help lists ui. -- CLI tests invoke the registered command. +- A command test invokes the registered command and verifies the home route. **Tests**: Command contract, tests/web-cli.test.ts **Gate**: npx vitest run tests/web-cli.test.ts -### T6: Add the request security guard +### T6: Add request security -**What**: Generate the per-start token and validate Host, token cookie, and Origin. +**What**: Create the per-start token and route guard for Host, cookie, Origin, and HTML response policy. **Where**: src/web/security.ts **Depends on**: T1 -**Reuses**: Node request headers and the configured bound port -**Requirement**: WEB-10, WEB-11, WEB-12, WEB-13, WEB-58 -**Tools**: MCP none; Skill tlc-spec-driven +**Reuses**: Node request headers and the actual bound port from the server +**Requirement**: WEB-10, WEB-11, WEB-12, WEB-13, WEB-71, WEB-72, WEB-74 **Done when**: -- Each server security instance creates a 32-byte random token. -- HTML responses can set the host-only HttpOnly, SameSite=Strict, Path=/ cookie. -- The guard accepts only the two allowed Host values with the bound port. -- Missing or stale token and missing or mismatched Origin return 403 for POST routes. -- A cookie from a prior server process is rejected by the current security instance. -- Security tests assert that rejected requests do not reach route handlers. +- Each security instance creates a token from 32 cryptographically random bytes. +- Host validation accepts only 127.0.0.1: and localhost:, case-insensitively. +- Every POST requires the codedeck_ui_token_ cookie and an HTTP Origin matching Host and the bound port. +- A valid t token on an HTML GET sets a host-only HttpOnly, SameSite=Strict, Path=/ cookie and returns HTTP 303 to the same path without t. +- An HTML GET without a valid token does not set the session cookie. +- HTML responses include Content-Security-Policy: frame-ancestors 'none'. +- Unit tests cover valid and invalid token bootstrap, Host, Origin, cookie name, redirect, and CSP behavior. **Tests**: Integration, tests/web-security.test.ts **Gate**: npx vitest run tests/web-security.test.ts ### T7: Apply security before route dispatch -**What**: Integrate the security guard into the shared server so every request gets a Host check and every POST gets token and Origin checks. +**What**: Integrate the guard into the shared server before any handler can run. **Where**: src/web/server.ts **Depends on**: T1, T6 **Reuses**: Route policies from src/web/security.ts -**Requirement**: WEB-10, WEB-12, WEB-13, WEB-58 -**Tools**: MCP none; Skill tlc-spec-driven +**Requirement**: WEB-10, WEB-12, WEB-13, WEB-71, WEB-73, WEB-74, WEB-75 **Done when**: -- The guard runs before route dispatch. -- Valid GET routes remain Host-checked and do not require a mutation token. -- Every POST route is rejected with 403 when its token or Origin check fails. -- A token issued by a previous server process cannot authorize a POST. -- Server-level tests cover the guard and route handler call count. +- The server obtains the actual port after listen and creates security with that port before opening or printing the URL. +- Every route gets a Host check before route dispatch. +- Invalid POST cookie or Origin requests return 403 without invoking the route handler. +- The opened and printed initial page URL includes ?t=; a browser-open failure prints the same full URL and keeps serving. +- Server tests prove rejected requests do not reach handlers and token URLs are passed to the browser opener and output. **Tests**: Integration, tests/web-security.test.ts and tests/web-server.test.ts **Gate**: npx vitest run tests/web-security.test.ts tests/web-server.test.ts -### T8: Create the pure setup planner +### T8: Add the pure setup planner and separate binding validator -**What**: Add a synchronous planner for full role, orchestrator, sandbox, autocompact, and profile selections. +**What**: Add a pure planner that applies setup selections to RunAgentConfig and a separate binding validation function used by callers. **Where**: src/config/setup-plan.ts -**Depends on**: None -**Reuses**: Existing config and profile helpers, diffConfig, and binding validation -**Requirement**: WEB-14, WEB-15, WEB-16, WEB-17, WEB-18, WEB-19, WEB-20, WEB-21, WEB-61 -**Tools**: MCP none; Skill tlc-spec-driven +**Depends on**: T7 +**Reuses**: RunAgentConfig, profile helpers, diffConfig, role bindings, and existing catalog validation rules +**Requirement**: WEB-14, WEB-15, WEB-16, WEB-17, WEB-18, WEB-19, WEB-61, WEB-62, WEB-86, WEB-87 **Done when**: -- The planner returns the whole proposed config, diff, and validation results. -- It performs no file, network, or discovery I/O. -- Autocompact on sets enabled=true; off sets enabled=false only when the current config already has an autocompact block. -- Tests cover each field, preservation of unselected values, profiles, diff paths, and rejected bindings. -- Rejected bindings cannot produce a saveable success result. +- buildSetupPlan accepts RunAgentConfig, a resolved target, and selections; it returns proposedConfig and diff only. +- The planner does not read or write files, discover models, or call catalog validation. +- Separate binding validation can be invoked by web apply and batch without being called by the planner. +- Planner tests cover selected values, skipped roles, unrelated config keys, profiles, and diff paths for each setup field. +- Planner tests prove typed or off-catalog binding values can be planned without catalog validation. **Tests**: Unit, tests/setup-plan.test.ts **Gate**: npx vitest run tests/setup-plan.test.ts -### T9: Reuse the planner from the terminal wizard +### T9: Reuse planning and separate validation in setup paths -**What**: Replace wizard-local config assembly with the shared planner and keep the same save boundary. +**What**: Route wizard config assembly through the pure planner and keep batch validation outside it. **Where**: src/cli/commands/setup.ts **Depends on**: T8 -**Reuses**: Existing wizard selection and persistence flow -**Requirement**: WEB-22 -**Tools**: MCP none; Skill tlc-spec-driven +**Reuses**: Existing wizard selection, runSetupBatch, and config save flow +**Requirement**: WEB-22, WEB-36 **Done when**: -- The wizard delegates completed selections to the planner. -- Discovery, abort, skip, save-failure, and profile behavior remain unchanged. -- Existing setup wizard and setup CLI contract cases remain unchanged and pass. +- The wizard passes its selections through buildSetupPlan before saving. +- The wizard keeps typed-model, role skip, effort skip, abort, discovery, save-failure, and profile behavior. +- runSetupBatch validates only the winning --bind value for each role outside the planner. +- Existing setup wizard and CLI contract cases pass unchanged. **Tests**: Integration, tests/setup-wizard.test.ts and tests/setup-cli-contract.test.ts **Gate**: npx vitest run tests/setup-wizard.test.ts tests/setup-cli-contract.test.ts -### T10: Add the setup page +### T10: Add the setup page and tested behavior logic -**What**: Add the self-contained setup page with all controls represented by the current wizard. +**What**: Add a self-contained setup HTML page whose exported TypeScript behavior functions also drive selection, refresh state, and 403 recovery in the browser. **Where**: src/web/setup-page.ts -**Depends on**: T8, T9 -**Reuses**: Setup selection semantics in src/cli/commands/setup.ts -**Requirement**: WEB-23, WEB-25, WEB-37, WEB-38, WEB-39, WEB-40, WEB-41, WEB-42 -**Tools**: MCP none; Skill tlc-spec-driven +**Depends on**: T8 +**Reuses**: Wizard selection semantics and the route contracts in spec.md +**Requirement**: WEB-23, WEB-25, WEB-37, WEB-38, WEB-39, WEB-40, WEB-41, WEB-64, WEB-65, WEB-66, WEB-67, WEB-68, WEB-73 **Done when**: -- The page offers every role's harness and model selection. -- It offers effort only for roles whose wizard has an effort screen. -- It offers all orchestrator parameters, both sandbox values, and both autocompact values. -- It displays the selected --profile target. -- Refresh shows “Discovering models...” until the response completes. -- Page tests assert controls, route requests, and no external assets. +- The page has a free-text harness:model field for every role, with supported effort choices and no effort control for opencode. +- It has role skip controls, orchestrator presets and custom parameters, both sandbox values, and autocompact on/off. +- Its state logic preserves skipped roles and effort; a first-run all-role skip can produce agents: {}. +- Positive finite custom parallelism is kept as a number in the proposal. +- A pending refresh exposes “Discovering models...” until the response completes. +- A protected POST response of 403 exposes the exact reload/restart message from WEB-73. +- The HTML injects its exported behavior function source; Node tests call those same functions with fake fetch and state callbacks without a DOM. **Tests**: Unit, tests/web-pages.test.ts **Gate**: npx vitest run tests/web-pages.test.ts -### T11: Add setup catalog and mutation handlers +### T11: Add setup state, catalog, and mutation routes -**What**: Implement GET catalog and protected POST refresh, dry-run, and apply handlers. +**What**: Add the setup route factory for state and catalog reads plus refresh, dry-run, and apply actions. **Where**: src/web/setup-routes.ts **Depends on**: T7, T8 -**Reuses**: Config store, getBatchModels, getCachedOrDiscoverModels, and buildSetupPlan -**Requirement**: WEB-24, WEB-25, WEB-26, WEB-27, WEB-28, WEB-29, WEB-30, WEB-31, WEB-32, WEB-33, WEB-59 -**Tools**: MCP none; Skill tlc-spec-driven +**Reuses**: readConfigForSetup, resolveSetupTarget, getBatchModels, buildSetupPlan, and separate binding validation +**Requirement**: WEB-20, WEB-21, WEB-24, WEB-26, WEB-27, WEB-28, WEB-29, WEB-30, WEB-31, WEB-32, WEB-33, WEB-59, WEB-63, WEB-76, WEB-77, WEB-84, WEB-85 **Done when**: -- GET catalog returns cached catalog data and status without discovery. -- POST refresh starts discovery and concurrent callers share its promise. -- A discovery error for one harness stays beside the other returned catalog entries. -- Dry-run returns a proposal, diff, and validations without writing. -- Apply writes only a valid non-empty proposal; an empty diff returns unchanged without writing. -- Malformed, oversized, invalid, and save-failure requests return the specified status and envelope. -- HTTP integration tests verify the saved config and every no-write path. +- GET /api/setup/state returns the global or named profile target and current bindings, effort, orchestrator, sandbox, and autocompact values for prefill. +- An active profile without a snapshot returns the existing SetupUsageError instead of selecting global config. +- GET /api/setup/catalog calls only getBatchModels with allowNetwork:false and returns its actual result fields. +- Protected refresh calls getBatchModels with refresh:true, allowNetwork:true, and timeoutMs:12000; incomplete discovery returns the helper's cache fallback and discoveryError without partial network results. +- Concurrent refresh callers share the in-flight promise. +- Dry-run returns an exact SetupEnvelope with proposta, validacoes, mudancas, and resultado and does not write config. +- Apply uses readConfigForSetup, validates only bindings whose harness or model changed, does not validate effort-only changes, and does not block unrelated changes for an unchanged off-catalog binding. +- Invalid JSON returns resultado.code=14 and a read error returns resultado.code=15; both paths do not write config. +- Empty diffs return unchanged and do not write; valid non-empty diffs save; malformed bodies return 400; validation failures return 422; save failures return 500. +- Tests cover state, cache and refresh results, concurrency, exact envelope keys, changed and unchanged off-catalog bindings, invalid/read errors, and every no-write path. **Tests**: Integration, tests/setup-web.test.ts **Gate**: npx vitest run tests/setup-web.test.ts -### T12: Make web setup the default command path +### T12: Make interactive setup open the web console -**What**: Add setup web flags and route startup while preserving --tui and the existing batch path. +**What**: Add setup web startup flags and preserve the confirmed TTY, TUI, and batch branches. **Where**: src/cli/commands/setup.ts **Depends on**: T9, T10, T11 **Reuses**: Existing argument parser and runSetupBatch -**Requirement**: WEB-34, WEB-35, WEB-36, WEB-42 -**Tools**: MCP none; Skill tlc-spec-driven +**Requirement**: WEB-09, WEB-34, WEB-35, WEB-36, WEB-42, WEB-69, WEB-70 **Done when**: -- codedeck setup without batch flags or --tui opens /setup. -- --tui selects the existing terminal wizard. -- Batch flags still call runSetupBatch and keep JSON, dry-run, binding, exit-code, and save behavior. -- --profile and --refresh reach the intended web setup target and catalog refresh. -- Existing setup CLI contract cases pass without changes. +- With both TTYs, no batch flags, and no --tui, codedeck setup starts the server at /setup. +- With --tui and both TTYs, setup runs the existing terminal wizard. +- Without both TTYs and without batch flags, setup exits 1 with the existing terminal message and starts no server. +- Batch flags continue to call runSetupBatch without changing JSON, dry-run, bind, validation, save, or exit-code contracts. +- --profile targets the selected profile and --refresh starts the protected catalog refresh after /setup loads. +- --json with --port returns a usage error before server startup. +- --port, --no-open, open failure, and printed token URL are covered by web command tests. +- Existing setup CLI contract tests pass without changes. **Tests**: Command contract, tests/web-cli.test.ts and tests/setup-cli-contract.test.ts **Gate**: npx vitest run tests/web-cli.test.ts tests/setup-cli-contract.test.ts -### T13: Add setup routes to ui +### T13: Register setup routes with ui -**What**: Register the setup page and setup API routes in the home command's route table. +**What**: Add the setup page and API routes to the route table used by codedeck ui. **Where**: src/cli/commands/ui.ts **Depends on**: T12 -**Reuses**: Existing home and review route registrations -**Requirement**: WEB-08, WEB-23 -**Tools**: MCP none; Skill tlc-spec-driven +**Reuses**: The registered review routes and setup route factory +**Requirement**: WEB-08, WEB-23, WEB-42, WEB-63 **Done when**: -- codedeck ui serves /setup and its API routes. -- The home page setup link returns the setup page. -- Command tests assert page status and route registration. +- codedeck ui serves /setup, /api/setup/state, /api/setup/catalog, and the setup action routes. +- The home page includes /setup only after that page route is registered. +- Command tests assert setup status and route registration. **Tests**: Integration, tests/web-cli.test.ts **Gate**: npx vitest run tests/web-cli.test.ts -### T14: Add the usage query handler +### T19: Update setup instructions in README + +**What**: Replace the picker-first setup description with browser setup as the interactive default and retain the --tui and --refresh options. +**Where**: README.md +**Depends on**: T12 +**Reuses**: Existing setup section at lines 149-186 +**Requirement**: WEB-82 + +**Done when**: + +- The example at line 154 describes codedeck setup opening the web console. +- The text at line 186 describes --refresh and points to --tui for the frozen terminal picker. +- No unrelated README sections change. + +**Tests**: none +**Gate**: git diff --check -- README.md + +### T14: Extract shared usage query parameter construction + +**What**: Extract buildUsageQueryParams(opts, cwd, now) from the aggregate CLI branch into the shared helper and use it from src/cli/commands/usage.ts. +**Where**: src/core/usage-query.ts +**Depends on**: None +**Reuses**: Existing aggregate filter precedence in src/cli/commands/usage.ts:129-164 +**Requirement**: WEB-44 + +**Done when**: + +- The pure function preserves all, today, days, default-today, current-over-repo, since, until, model, and agent behavior. +- src/cli/commands/usage.ts calls the shared function with parsed options, process.cwd(), and the current date. +- The same options, cwd, and now produce identical UsageQueryParams for CLI and web callers. +- tests/usage-cli.test.ts covers the helper and CLI parity cases. +- Before P5 starts, origin/feat/orchestrator-usage is an ancestor of HEAD. + +**Tests**: Unit and command contract, tests/usage-cli.test.ts +**Gate**: git merge-base --is-ancestor origin/feat/orchestrator-usage HEAD && npx vitest run tests/usage-cli.test.ts -**What**: Translate browser filters into UsageQueryParams and call an injected usage query function. +### T15: Add the usage query route + +**What**: Map GET /api/usage filters through buildUsageQueryParams and call the injected usage query function. **Where**: src/web/usage-routes.ts -**Depends on**: T7, T13 -**Reuses**: UsageQueryParams and fetchUsageQuery +**Depends on**: T7, T14 +**Reuses**: buildUsageQueryParams and fetchUsageQuery **Requirement**: WEB-44 -**Tools**: MCP none; Skill tlc-spec-driven **Done when**: -- The handler maps every supported aggregate filter to the same value used by the CLI. -- --current resolves to the command process working directory. -- Query errors return a JSON error response. -- Tests cover each filter and the successful query result. +- Query parsing maps period, repo, model, agent, since, until, all, today, days, and current to the shared builder input. +- --current resolves to the CLI process working directory and overrides repo. +- Successful results retain the UsageQueryResult shape. +- Query errors return an HTTP 500 JSON error. +- tests/usage-web.test.ts covers each filter, the current override, success, and error response. **Tests**: Integration, tests/usage-web.test.ts **Gate**: npx vitest run tests/usage-web.test.ts -### T15: Add the usage page +### T16: Add usage page and tested behavior logic -**What**: Add the self-contained usage page with totals, all current breakdowns, optional byOrigin, and polling. +**What**: Add the self-contained usage page and its exported filter, render-state, and polling functions. **Where**: src/web/usage-page.ts -**Depends on**: T14 -**Reuses**: UsageQueryResult and UsageMetricBucket -**Requirement**: WEB-43, WEB-45, WEB-46, WEB-47, WEB-48, WEB-49, WEB-50, WEB-51, WEB-52, WEB-53, WEB-60 -**Tools**: MCP none; Skill tlc-spec-driven +**Depends on**: T15 +**Reuses**: UsageQueryResult, UsageTotals, and UsageMetricBucket +**Requirement**: WEB-43, WEB-45, WEB-46, WEB-47, WEB-48, WEB-49, WEB-50, WEB-51, WEB-52, WEB-53, WEB-60, WEB-78, WEB-80, WEB-81 **Done when**: -- The page renders every UsageTotals field and each of the five existing breakdown arrays. -- It renders byOrigin when present and renders the other sections when absent. -- It polls the active filter set every 2 seconds by default and honors --interval. -- Query errors leave the last successful result visible and show the error. -- Page tests cover origin-present, origin-absent, polling, and error states. +- Page logic exposes every UsageTotals field and each of byDay, byRepository, byModel, byAgent, and byRun. +- It exposes byOrigin when present and handles its absence from an older daemon without throwing. +- Period, repo, model, agent, since, and until changes each issue a query with the updated filters. +- Polling refreshes the active filter set and normalizes the interval with Math.max(1, Number(opts.interval) || 2). +- A query error leaves the last successful result visible and stores the error state. +- --by origin selects origin initially while every available breakdown remains reachable. +- The HTML injects the exported behavior function source; tests directly call that function in Node with fake fetch, timer, and render adapters. **Tests**: Unit, tests/web-pages.test.ts **Gate**: npx vitest run tests/web-pages.test.ts -### T16: Add usage --web command wiring +### T17: Add usage --web command wiring -**What**: Add --web, --port, and --no-open behavior to aggregate usage while preserving CLI output paths. +**What**: Add aggregate usage web startup while keeping single-run and post-merge CLI branches ahead of it. **Where**: src/cli/commands/usage.ts -**Depends on**: T14, T15 -**Reuses**: Existing usage option parser, fetchUsageQuery, and single-run branch -**Requirement**: WEB-54, WEB-55, WEB-56 -**Tools**: MCP none; Skill tlc-spec-driven +**Depends on**: T14, T15, T16 +**Reuses**: Shared server, usage route factory, and existing command options +**Requirement**: WEB-09, WEB-54, WEB-55, WEB-56, WEB-80, WEB-81, WEB-83 **Done when**: -- --web opens /usage and forwards aggregate filters. -- Without --web the existing snapshot, TUI, watch, plain, and JSON paths remain in place. -- Positional and --run IDs keep the current single-run branch, including when --web is also supplied. -- Usage and statusline command contract tests pass unchanged. +- Aggregate usage with --web starts /usage and passes the same date, repository, model, agent, and --by filters. +- --port and --no-open use the shared server; --interval uses the existing normalization rule. +- --web --json for aggregate usage starts the page and prints its token URL. +- When --backfill is absent, positional ID or --run takes the usage.get path before web startup, preserves --observe and --json, ignores aggregate-only --by origin, and starts no server. +- --backfill runs before web startup and starts no server even with --web. +- Without --web, snapshot, TUI, watch, plain, JSON, --by origin, --observe, and --backfill contracts remain unchanged. +- tests/web-cli.test.ts proves usage --web --json calls usage.get and starts no server. +- tests/usage-cli.test.ts covers --by origin, --observe, --backfill, and --interval contracts. -**Tests**: Command contract, tests/web-cli.test.ts and tests/usage-statusline-contract.test.ts -**Gate**: npx vitest run tests/web-cli.test.ts tests/usage-statusline-contract.test.ts +**Tests**: Command contract, tests/web-cli.test.ts and tests/usage-cli.test.ts +**Gate**: npx vitest run tests/web-cli.test.ts tests/usage-cli.test.ts -### T17: Add usage routes to ui +### T18: Register usage routes with ui -**What**: Register the usage page and API route in the home command's route table. +**What**: Register /usage and /api/usage after their route factories are available. **Where**: src/cli/commands/ui.ts -**Depends on**: T13, T16 -**Reuses**: Existing home, review, and setup route registrations +**Depends on**: T13, T15, T16 +**Reuses**: Existing ui route table and the home page renderer **Requirement**: WEB-08, WEB-43 -**Tools**: MCP none; Skill tlc-spec-driven **Done when**: - codedeck ui serves /usage and /api/usage. -- The home page usage link returns the usage page. -- Command tests assert page status and route registration. +- The home page links to /usage only after its page route is registered. +- tests/web-cli.test.ts asserts both route status and the home link. + +**Tests**: Integration, tests/web-cli.test.ts +**Gate**: npx vitest run tests/web-cli.test.ts + -**Tests**: Integration, tests/web-cli.test.ts and tests/web-server.test.ts -**Gate**: npx vitest run tests/web-cli.test.ts tests/web-server.test.ts +## Diagram-Definition Cross-Check + +| Task | Depends on | Diagram edges | Status | +| --- | --- | --- | --- | +| T1 | None | None | Match | +| T2 | T1 | T1 -> T2 | Match | +| T3 | T1 | T1 -> T3 | Match | +| T4 | T2, T3 | T2 -> T4; T3 -> T4 | Match | +| T5 | T4 | T4 -> T5 | Match | +| T6 | T1 | T1 -> T6 | Match | +| T7 | T1, T6 | T1 -> T7; T6 -> T7 | Match | +| T8 | T7 | T7 -> T8 | Match | +| T9 | T8 | T8 -> T9 | Match | +| T10 | T8 | T8 -> T10 | Match | +| T11 | T7, T8 | T7 -> T11; T8 -> T11 | Match | +| T12 | T9, T10, T11 | T9 -> T12; T10 -> T12; T11 -> T12 | Match | +| T13 | T12 | T12 -> T13 | Match | +| T14 | None | None | Match | +| T15 | T7, T14 | T7 -> T15; T14 -> T15 | Match | +| T16 | T15 | T15 -> T16 | Match | +| T17 | T14, T15, T16 | T14 -> T17; T15 -> T17; T16 -> T17 | Match | +| T18 | T13, T15, T16 | T13 -> T18; T15 -> T18; T16 -> T18 | Match | +| T19 | T12 | T12 -> T19 | Match | + +## Test Co-location Validation + +| Task | Code layer | Matrix requires | Task tests | Status | +| --- | --- | --- | --- | --- | +| T1 | Web server/router | Integration | tests/web-server.test.ts | OK | +| T2 | HTML pages and page behavior | Unit | tests/web-pages.test.ts | OK | +| T3 | Web server/router | Integration | tests/review.test.ts, tests/review-command.test.ts | OK | +| T4 | CLI wiring | Command contract | tests/web-cli.test.ts | OK | +| T5 | CLI wiring | Command contract | tests/web-cli.test.ts | OK | +| T6 | Security | Integration | tests/web-security.test.ts | OK | +| T7 | Security | Integration | tests/web-security.test.ts, tests/web-server.test.ts | OK | +| T8 | Setup core | Unit | tests/setup-plan.test.ts | OK | +| T9 | Setup core | Integration | tests/setup-wizard.test.ts, tests/setup-cli-contract.test.ts | OK | +| T10 | HTML pages and page behavior | Unit | tests/web-pages.test.ts | OK | +| T11 | Setup web handlers | Integration | tests/setup-web.test.ts | OK | +| T12 | CLI wiring | Command contract | tests/web-cli.test.ts, tests/setup-cli-contract.test.ts | OK | +| T13 | CLI wiring | Command contract | tests/web-cli.test.ts | OK | +| T14 | Usage query builder | Unit and command contract | tests/usage-cli.test.ts | OK | +| T15 | Usage web handler | Integration | tests/usage-web.test.ts | OK | +| T16 | HTML pages and page behavior | Unit | tests/web-pages.test.ts | OK | +| T17 | CLI wiring | Command contract | tests/web-cli.test.ts, tests/usage-cli.test.ts | OK | +| T18 | CLI wiring | Command contract | tests/web-cli.test.ts | OK | +| T19 | Documentation | none | none | OK | From 4e841d143715a4fad8f4ebbffe5dd4bafe3d479b Mon Sep 17 00:00:00 2001 From: 4ndreello <4ndreello@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:32:00 -0300 Subject: [PATCH 3/3] docs(web): Resolve second web console spec review --- .specs/features/web-console/design.md | 62 ++++++++++++---------- .specs/features/web-console/spec.md | 65 ++++++++++++++--------- .specs/features/web-console/tasks.md | 76 ++++++++++++++++----------- 3 files changed, 119 insertions(+), 84 deletions(-) diff --git a/.specs/features/web-console/design.md b/.specs/features/web-console/design.md index b59de19..be6e0df 100644 --- a/.specs/features/web-console/design.md +++ b/.specs/features/web-console/design.md @@ -53,12 +53,12 @@ The existing review command uses Node's built-in HTTP server and browser opener. | Setup wizard selections | src/cli/commands/setup.ts:499-801 | Keep its harness, model, effort, orchestrator, sandbox, autocompact, skip, and profile choices. Pass completed selections through the planner before saving. | | Picker free-text behavior | src/cli/picker-state.ts:113-141 | Preserve typed harness:model values, including models absent from the catalog. | | Profile target resolution | src/cli/commands/setup.ts:831-853 | Use the current explicit and active profile rules for setup state and planning. | -| Setup envelope and batch validation | src/cli/commands/setup.ts:1000-1036, 1402-1459 | Preserve the exact envelope fields and keep batch validation on winning --bind values outside the planner. | +| Setup batch validation | src/cli/commands/setup.ts:1402-1459 | Keep validation on winning --bind values in the batch caller and use the shared helpers from src/config/setup.ts. | | Setup config reads | src/config/config.ts:391-405, 531-540 | Use readConfigForSetup for web state and apply so invalid JSON and read errors are not replaced by default config. Do not use loadConfig for apply. | -| Setup diffs and profile snapshots | src/config/config.ts:48-76, 103-137, 237-257, 543-548 | Use RunAgentConfig, existing profile helpers, and diffConfig to produce and save the same config shape. | +| Setup diff, target, envelope, and validation helpers | Current implementations are src/cli/commands/setup.ts:831-853, 1000-1036, 1229-1302; profile helpers are src/config/config.ts:48-76, 103-137, 237-257, 543-548 | Move resolveSetupTarget, SetupEnvelope, diffConfig, catalogContains, and validateBindings into src/config/setup.ts so both CLI and web callers import them without a config-to-CLI dependency. | | Model catalog | src/core/models.ts:276-389 | Use getBatchModels for catalog reads and refresh. | -| Usage query and fallback | src/cli/commands/usage.ts:37-77 | Reuse fetchUsageQuery so CLI and web keep daemon usage.query and read-only SQLite fallback behavior. | -| Usage query and result types | src/daemon/protocol.ts:160-220 | Use UsageQueryParams and UsageQueryResult. The merged type requires byOrigin; tolerate its absence at runtime for an older daemon over IPC. | +| Usage query and fallback | src/cli/commands/usage.ts:65-105 | Reuse fetchUsageQuery so CLI and web keep daemon usage.query and read-only SQLite fallback behavior. | +| Usage query and result types | src/daemon/protocol.ts:169-221 | Use UsageQueryParams and UsageQueryResult. Main already requires byOrigin at 80ec486 (#103); tolerate its absence only when an older daemon process answers over IPC. | | Existing command and behavior tests | tests/review.test.ts, tests/review-command.test.ts, tests/setup-wizard.test.ts, tests/setup-cli-contract.test.ts, tests/usage-cli.test.ts | Preserve current contract assertions and add focused tests for new server, page logic, route handlers, and CLI options. | | Setup README section | README.md:149-186 | Replace the picker-first description with web setup as the default and retain --tui and --refresh guidance. | @@ -121,19 +121,19 @@ The home page does not hard-code future routes. The initial ui command links onl ### Setup planner -- **Purpose**: Apply setup selections to an already-resolved RunAgentConfig and return a proposed config and diff without I/O or catalog validation. -- **Location**: src/config/setup-plan.ts +- **Purpose**: Apply setup selections to RunAgentConfig and return a proposed config and diff without I/O or catalog validation. +- **Location**: src/config/setup.ts - **Interfaces**: - buildSetupPlan(currentConfig, targetProfile, selections) returns proposedConfig and diff. - - SetupSelection represents selected bindings, effort values, orchestrator mode and parameters, sandbox, and autocompact. -- **Dependencies**: RunAgentConfig, Role, role binding, orchestrator, sandbox, and autocompact types; profile snapshot helpers; diffConfig. -- **Reuses**: Existing setup merge rules and config diff behavior. + - resolveSetupTarget applies explicit and active profile rules. + - SetupSelection represents selected bindings, effort values, optional orchestrator mode and parameters, sandbox, autocompact, and per-role off-catalog confirmation. + - SetupEnvelope, diffConfig, catalogContains, and validateBindings are exported for the CLI and web routes. +- **Dependencies**: RunAgentConfig, Role, role binding, orchestrator, sandbox, and autocompact types; profile snapshot helpers; cached catalog result types. +- **Reuses**: Existing setup merge, target resolution, envelope, binding validation, and config diff behavior. -The planner accepts a RunAgentConfig, never SetupConfigRead. It performs no filesystem, network, model discovery, or catalog validation. The terminal wizard and web flow pass the same selection model into it. The wizard keeps its current typed-model behavior and does not gain catalog validation. +Config reads happen before planning through readConfigForSetup. The shared config helper resolves explicit and active profiles using the current rules. An absent active profile keeps the existing SetupUsageError. A first-run target with all roles skipped keeps agents: {} as the empty sentinel. Turning autocompact off writes enabled=false only when the target already has an autocompact block. An omitted orchestrator selection preserves the target's current value or its absence, so an otherwise untouched selection has an empty diff. -Profile resolution and config reading happen before planning. An explicit profile uses the existing profile snapshot rules. An absent active profile keeps the existing SetupUsageError. A first-run target with all roles skipped keeps agents: {} as the empty sentinel. Turning autocompact off writes enabled=false only when the target already has an autocompact block. - -The planner does not produce binding validation results. Batch continues to validate only the winning --bind value for each role. Web apply compares each selected binding with the resolved target, validates only bindings whose harness or model changed, and does not validate effort-only changes. As a result, an unchanged off-catalog binding does not block a sandbox or other unrelated change. +The planner does not produce binding validation results. Batch continues to validate only the winning --bind value for each role. Web apply compares each selected binding with the resolved target, then validates changed harness:model bindings against getBatchModels with allowNetwork:false. It does not validate effort-only changes. If a changed model is absent from that cached catalog, apply returns HTTP 422 unless the request includes offCatalogConfirmed[role]=true. The page sends that per-role confirmation only after an explicit user confirmation, matching the wizard's second Enter. An unchanged off-catalog binding does not block a sandbox or other unrelated change. ### Setup page and API @@ -147,15 +147,15 @@ The planner does not produce binding validation results. Batch continues to vali - **Dependencies**: readConfigForSetup, resolveSetupTarget, getBatchModels, separate binding validation, buildSetupPlan, and the config writer. - **Reuses**: Existing setup config, profile, diff, validation codes, and SetupEnvelope. -GET /api/setup/state reads with readConfigForSetup and reports whether the resolved target is global or a named profile. It returns current bindings, per-role effort, orchestrator, sandbox, and autocompact values. An active profile name without a snapshot returns the existing SetupUsageError. An explicit profile without a saved snapshot uses the current profile defaults. +GET /api/setup/state reads with readConfigForSetup and reports whether the resolved target is global or a named profile. It returns current bindings, per-role effort, orchestrator, sandbox, and autocompact values. Invalid config JSON returns code 14, and a config read error returns code 15. An active profile name without a snapshot returns the existing SetupUsageError with code 14 on apply. An explicit profile without a saved snapshot uses the current profile defaults. -GET /api/setup/catalog calls getBatchModels with allowNetwork:false and returns its models, status, source, ageMs, cacheWriteFailed, and discoveryError when present. The protected POST /api/setup/catalog/refresh calls getBatchModels with refresh:true, allowNetwork:true, and timeoutMs:12000. If discovery is incomplete or a requested harness reports an error, getBatchModels returns its cache fallback and discoveryError without partial network results. Concurrent refresh requests share one in-flight promise. +GET /api/setup/catalog calls getBatchModels with allowNetwork:false and returns its models, status, source, ageMs, cacheWriteFailed, and discoveryError when present. The protected POST /api/setup/catalog/refresh calls getBatchModels with refresh:true, allowNetwork:true, and timeoutMs:12000. If discovery is incomplete or a requested harness reports an error, getBatchModels returns its refresh fallback and discoveryError without partial network results. A refresh can return unavailable without previously fresh cache entries because getBatchModels drops fresh entries from its refresh fallback; the page keeps its already loaded catalog and shows discoveryError in that case. Concurrent refresh requests share one in-flight promise. -Dry-run calls the planner and separate validator, then returns an object with the exact SetupEnvelope fields proposta, validacoes, mudancas, and resultado. It never writes config. Apply uses the same proposal and validation steps, and writes only if the diff is non-empty and every changed binding validates. An empty diff returns unchanged without a write. +Dry-run calls the planner and separate validator, then returns an object with the exact SetupEnvelope fields proposta, validacoes, mudancas, and resultado. It never writes config. Invalid JSON returns resultado.code=14 and a read error returns resultado.code=15. Apply uses the same proposal and validation steps, and writes only if the diff is non-empty and every changed binding validates. An empty diff returns unchanged without a write. -The route reads config through readConfigForSetup, never loadConfig. Invalid JSON returns resultado.code=14 and a read error returns resultado.code=15, with no write in either case. Malformed or oversized requests return HTTP 400. Validation failures return HTTP 422 with the existing code and saved=false. Save failures return HTTP 500 with saved=false. The setup API has one route factory, createSetupRoutes. +The route reads config through readConfigForSetup, never loadConfig. Dry-run and apply return resultado.code=14 for invalid JSON and resultado.code=15 for a read error, without writing. GET /api/setup/state returns the matching code in its JSON error. Malformed or oversized requests return HTTP 400. Binding validation failures return HTTP 422 with the existing code and saved=false. Save failures return HTTP 500 with saved=false. The setup API has one route factory, createSetupRoutes. -Setup selection, error rendering, and refresh state live in pure TypeScript page behavior functions. The HTML string injects those same function sources with Function.prototype.toString(). Node tests call the functions with fake fetch and timer adapters, then assert state changes and requests directly. HTML substring checks may cover static markup but do not stand in for these behavior tests. A protected POST 403 changes page state to the reload/restart message from WEB-73. +Setup selection, error rendering, and refresh state live in pure TypeScript page behavior functions. The HTML string injects those same function sources with Function.prototype.toString(). Node tests call the functions with fake fetch and timer adapters, then assert state changes and requests directly. Tests also extract the inline script from SETUP_PAGE and USAGE_PAGE, evaluate it in node:vm with a clean context plus stubbed fetch, timers, and document, then call the page functions from that context. This proves the injected code runs without module-scope dependencies. HTML substring checks may cover static markup but do not stand in for these behavior tests. A protected POST 403 changes setup page state to the reload/restart message from WEB-73. ### Usage query parameter builder @@ -164,7 +164,7 @@ Setup selection, error rendering, and refresh state live in pure TypeScript page - **Interfaces**: - buildUsageQueryParams(opts, cwd, now) returns UsageQueryParams. - **Dependencies**: UsageQueryParams and UsagePeriod from src/daemon/protocol.ts. -- **Reuses**: The aggregation logic currently in src/cli/commands/usage.ts:129-164. +- **Reuses**: The aggregate filter logic currently in src/cli/commands/usage.ts:175-211. The CLI passes parsed options, process.cwd(), and the current date. The web handler converts its query string to the same option shape and passes its working directory and current date to the builder. The builder keeps the existing precedence: --all, --today, --days, then default today when since is absent. It maps 3, 7, and 30 to named periods, other positive day counts to a local-midnight since value, lets --current override --repo, and passes through since, until, model, and agent. @@ -180,13 +180,13 @@ The CLI passes parsed options, process.cwd(), and the current date. The web hand - **Dependencies**: buildUsageQueryParams and injected fetchUsageQuery. - **Reuses**: UsageQueryParams, UsageQueryResult, UsageTotals, and UsageMetricBucket from src/daemon/protocol.ts. -The endpoint returns the same UsageQueryResult as the aggregate CLI. After feat/orchestrator-usage merges, byOrigin is required by the TypeScript type. The page reads result.byOrigin ?? [] at runtime because an older daemon over IPC may omit the property. +The endpoint returns the same UsageQueryResult as the aggregate CLI. Main already has the required byOrigin type at src/daemon/protocol.ts:220 from commit 80ec486 (#103). The page reads result.byOrigin ?? [] only because an older daemon process over IPC may omit the property. The page has controls for period, repo, model, agent, since, and until. A change to any control re-queries with the current filter set. The selected --by value is the initial highlighted breakdown; the page keeps every breakdown section accessible. It shows every UsageTotals field and the byDay, byRepository, byModel, byAgent, byRun, and when present byOrigin arrays. The page behavior functions own query state, filter changes, polling, render data, and error state. The HTML injects the same function source that Node tests import and call with fake fetch, timers, and render callbacks. Tests assert re-query-on-change, interval normalization, polling, origin-present and origin-absent results, and retention of the last good result after a failed query. -Polling uses Math.max(1, Number(opts.interval) || 2). Commander supplies the default string "2", so the implementation cannot distinguish an omitted option from explicit --interval 2. Zero and NaN resolve to 2 seconds; negative values and positive values below 1 resolve to 1 second. For aggregate --web calls, the page refreshes itself; --watch does not select terminal rendering. A positional run ID or --run stays on usage.get and starts no server even with --web. --backfill also runs before web startup. --observe and --json retain their post-merge single-run behavior. +Polling uses Math.max(1, Number(opts.interval) || 2). Commander supplies the default string "2", so the implementation cannot distinguish an omitted option from explicit --interval 2. Zero and NaN resolve to 2 seconds; negative values and positive values below 1 resolve to 1 second. For aggregate --web calls, the page refreshes itself; --watch does not select terminal rendering. A positional run ID or --run stays on usage.get and starts no server even with --web. --backfill also runs before web startup. --observe and --json retain their current single-run behavior. ### CLI wiring @@ -209,13 +209,14 @@ Polling uses Math.max(1, Number(opts.interval) || 2). Commander supplies the def ~~~typescript interface SetupSelection { agents: Partial> - orchestrator: OrchestratorMode + orchestrator?: OrchestratorMode sandbox?: RunAgentConfig["defaultSandbox"] autocompact?: RunAgentConfig["autocompact"] + offCatalogConfirmed?: Partial> } ~~~ -Missing role entries mean the user skipped that role. A profile name is supplied by CLI target resolution and is never accepted from the browser request body. A first-run config with no selected roles retains the agents: {} sentinel. +Missing role entries mean the user skipped that role. An omitted orchestrator preserves its current value or absence. offCatalogConfirmed records explicit per-role confirmation for changed models absent from the cached catalog. A profile name is supplied by CLI target resolution and is never accepted from the browser request body. A first-run config with no selected roles retains the agents: {} sentinel. ### Setup plan @@ -230,7 +231,7 @@ The current config input is RunAgentConfig. Catalog validation and SetupConfigRe ### Usage result -The route returns UsageQueryResult. In the post-merge type, byOrigin is required and contains UsageMetricBucket[]. At runtime, the page treats an absent field as an empty origin breakdown to support an older daemon process. +The route returns UsageQueryResult. The type in main requires byOrigin and contains UsageMetricBucket[]. At runtime, the page treats an absent field as an empty origin breakdown only for an older daemon process. ## Error handling strategy @@ -256,7 +257,7 @@ The route returns UsageQueryResult. In the post-merge type, byOrigin is required | loadConfig swallows read and parse errors. | src/config/config.ts:531-540 | Web apply could treat invalid config as defaults and overwrite it. | Use readConfigForSetup and test codes 14 and 15 before any write. | | The current review handler calls process.exit during shutdown. | src/cli/commands/review.ts:128-131 | Signal handling is hard to assert and can terminate a test runner. | Inject close and exit functions into the shared server. | | Model discovery can return incomplete results after network errors. | src/core/models.ts:329-389 | A UI that displays partial provider data would imply a complete catalog. | Use getBatchModels only and render its fallback plus discoveryError. | -| The post-merge usage type requires byOrigin, but an older daemon can answer IPC without the field. | src/daemon/protocol.ts:220; feat/orchestrator-usage | Direct access can crash page rendering against that daemon. | Keep the type required and use a runtime fallback in the page. | +| UsageQueryResult in main requires byOrigin, but an older daemon process can answer IPC without the field. | src/daemon/protocol.ts:208-221; commit 80ec486 (#103) | Direct access can crash page rendering against an older process. | Keep the type required and use a runtime fallback only for that daemon response. | | Page behavior currently has no DOM test dependency. | tests/review.test.ts, tests/setup-wizard.test.ts | Static markup checks cannot prove polling or state transitions. | Test exported pure page functions with fake fetch and timers in Node. | ## Tech decisions @@ -274,16 +275,19 @@ The route returns UsageQueryResult. In the post-merge type, byOrigin is required | Action security | Host-check every route; require cookie and matching HTTP Origin on every POST. | Read routes remain locally constrained and all mutating routes share one rule. | | HTML framing | Add Content-Security-Policy: frame-ancestors 'none' to every HTML response. | Local pages should not be embedded by another origin. | | Catalog source | Use getBatchModels for both GET and refresh. | One helper defines cache, discovery timeout, incomplete-result, and fallback behavior. | +| Setup apply catalog | Call getBatchModels with allowNetwork:false when validating changed bindings. | Apply checks the cache and does not start discovery. | | Planner boundary | Accept RunAgentConfig and selections; return proposed config and diff only. | Validation depends on the selected catalog and belongs in separate callers. | -| Validation | Web validates changed harness/model pairs only; batch validates winning --bind entries; wizard adds no catalog validation. | This keeps unchanged off-catalog config and existing wizard behavior intact. | -| Page tests | Export behavior functions from the page module and inject their source into HTML. | Vitest can test actual state logic under Node without a DOM package. | +| Validation | Web validates changed harness:model pairs against the cached catalog with allowNetwork:false and requires per-role confirmation for off-catalog models; batch validates winning --bind entries; wizard adds no catalog validation. | This keeps web apply offline, preserves explicit user choice, and leaves existing wizard behavior intact. | +| Page tests | Export behavior functions from the page module and inject their source into HTML. | Node tests call the functions directly and run extracted inline scripts in node:vm without a DOM package. | | Usage query params | Extract buildUsageQueryParams into src/core/usage-query.ts and call it from CLI and web. | A pure shared function makes filter mapping and parity testable. | -| Usage origin | Require byOrigin in the post-merge type; handle a missing runtime field from an older daemon. | Compile-time post-merge contracts and runtime daemon compatibility both remain explicit. | +| Usage origin | Use the required byOrigin type already present in main at 80ec486 (#103); handle a missing runtime field only from an older daemon process. | Current callers use the complete type while IPC remains compatible with an earlier running daemon. | | Aggregate usage --json | With --web, open /usage and print the token URL; retain JSON for aggregate calls without --web and for single-run calls. | The web option selects the aggregate interface while run-id callers retain their exact JSON path. | | Usage --by | Use --by as the initially highlighted breakdown and keep all sections accessible. | The browser can show other metrics without discarding the CLI preference. | | Usage polling | Use Math.max(1, Number(opts.interval) || 2); --watch does not select terminal output with --web. | This retains the current CLI normalization and default string behavior. | | Setup --refresh | Open /setup and start a protected catalog refresh on page load. | It retains the existing flag without adding a discovery job API. | | Setup --json with --port | Reject before starting a web server. | --json selects the batch interface and --port selects the web interface. | +| Setup batch flags with --port | Reject --dry-run and --non-interactive with --port before starting a server. | Both flags select the batch path. | +| Usage --web with --tui | Return a usage error before starting a web server. | One invocation cannot select the browser and terminal dashboard together. | | Setup request size | Limit JSON bodies to 64 KiB. | The complete role selection is bounded and parseable before planning. | | Setup HTTP status | Use 400 for malformed input, 422 for validation failures, and 500 for save failures. | Browser callers receive stable transport statuses while resultado.code retains CLI codes. | @@ -295,6 +299,6 @@ The route returns UsageQueryResult. In the post-merge type, byOrigin is required | P2 | Host, token, Origin, CSP, and server guard | P1 | | P3 | Pure setup planner and wizard reuse | P2 | | P4 | Setup page, routes, command wiring, and README update | P3 | -| P5 | Usage page, routes, command wiring, and query parity | P4 and merge of feat/orchestrator-usage into implementation HEAD | +| P5 | Usage page, routes, command wiring, and query parity | P4 | These artifacts stop at planning. They do not authorize source, test, plugin, or README changes during this documentation correction. diff --git a/.specs/features/web-console/spec.md b/.specs/features/web-console/spec.md index 108e1c6..b6d0d72 100644 --- a/.specs/features/web-console/spec.md +++ b/.specs/features/web-console/spec.md @@ -17,8 +17,8 @@ Setup and usage analytics currently require the terminal, while review already s - Review owns its Node HTTP server, port parser, browser opener, and request handler in src/cli/commands/review.ts:1-135. It defaults to port 3100, accepts --port and --no-open, binds to 127.0.0.1, serves the same review page at GET / and GET /review, and serves read-only GET /api/review. Existing tests are in tests/review.test.ts:193-230 and tests/review-command.test.ts:5-27. - src/web/review-page.ts:8-662 exports one self-contained HTML string with inline CSS and JavaScript. -- Usage option parsing and CLI branches are in src/cli/commands/usage.ts:13-30 and :79-214. fetchUsageQuery at :37-77 uses daemon IPC usage.query and falls back to read-only SQLite. The positional run-id path calls usage.get at :103-125. -- At this worktree HEAD, src/daemon/protocol.ts:199-211 has totals and byDay, byRepository, byModel, byAgent, and byRun. The feat/orchestrator-usage change adds required byOrigin to UsageQueryResult, --backfill, --observe, and --by origin. The web implementation must be based on a HEAD that contains that change. An older daemon reached over IPC may still return a result without byOrigin at runtime, so the page handles that field defensively. +- Usage option parsing and CLI branches are in src/cli/commands/usage.ts:108-130 and :131-261. fetchUsageQuery at :65-105 uses daemon IPC usage.query and falls back to read-only SQLite. The positional run-id path calls usage.get at :144-173. +- src/daemon/protocol.ts:208-221 defines UsageQueryResult with byOrigin. Usage origin support, --backfill, --observe, and --by origin are already in main as squash commit 80ec486 (#103), so P5 has no pending branch dependency. An older daemon process reached over IPC may omit byOrigin, and the page handles that runtime case with an empty origin breakdown. - The setup command and wizard are in src/cli/commands/setup.ts:625-801 and :1560-1626. A non-batch invocation without both stdin and stdout TTYs exits with code 1 and prints “setup needs a terminal on both stdin and stdout”; it starts no discovery and writes no config. The behavior is covered by tests/setup-cli-contract.test.ts:315-334 and tests/setup-wizard.test.ts:1220-1243. - The setup wizard allows typed harness:model values through src/cli/picker-state.ts:113-141 and keeps configured models that are absent from the catalog in src/cli/commands/setup.ts:499-518. It also preserves a role's binding and effort when their screens are skipped; first-run skips produce an empty agents object sentinel. Tests are in tests/setup-wizard.test.ts:579-592, :757-764, and :1179-1183. - The setup command resolves explicit and active profiles in src/cli/commands/setup.ts:831-853. An active profile that does not exist raises SetupUsageError. An explicit profile without a saved snapshot starts from profile defaults with agents: {}. @@ -66,11 +66,12 @@ Setup and usage analytics currently require the terminal, while review already s | Usage interval | Use a floor of 1 second and a numeric-conversion fallback of 2 seconds for web polling. | Commander supplies the default string "2", so the implementation cannot distinguish an omitted option from explicit --interval 2. | No | | Aggregate usage --web --json | Start the browser page and print the token URL; --json affects aggregate output only when --web is absent. | The browser is the selected aggregate interface, while the positional run-id JSON contract remains unchanged. | No | | Catalog refresh method | Use protected POST /api/setup/catalog/refresh with refresh:true, allowNetwork:true, and timeoutMs:12000. | Discovery may update the model cache, so it uses the guarded action path and the helper's current timeout. | No | +| Web apply catalog validation | Validate changed harness:model bindings against the cached catalog with getBatchModels({allowNetwork:false}); reject an off-catalog model with HTTP 422 unless the request includes explicit confirmation for that role. | This keeps apply offline and mirrors the wizard's second-Enter confirmation for typed models absent from its catalog. | Yes | | Page behavior tests | Put state, rendering decisions, and polling transitions in TypeScript functions exported from each page module, then inject their source into its HTML string. | Node tests can exercise the same functions without installing a DOM implementation. | No | -| Post-merge usage result | Use the merged required byOrigin type, with a runtime fallback when an older daemon omits the field. | The feature branch adds byOrigin to the type; IPC can still reach an older daemon process. | Yes | +| Usage origin dependency | Use the required byOrigin type already present in main at 80ec486 (#103); tolerate an absent field only when an older daemon process answers over IPC. | The implementation branch has the usage changes already, while an older running daemon can still use the earlier protocol shape. | Yes | | Usage query failure | Return HTTP 500 with a JSON error, show the error in the page, and retain the last successful result. | A later poll can recover without removing the visible result. | No | | Usage run ID with --web | When --backfill is absent, preserve the usage.get single-run branch, including --observe and --json, and start no web server when a positional ID or --run is present; --by origin remains aggregate-only. | The branch is used by the statusline and is evaluated before aggregate web startup. | No | -| Usage --backfill with --web | Run the existing backfill branch and start no web server. | The post-merge CLI checks --backfill before single-run and aggregate handling. | No | +| Usage --backfill with --web | Run the existing backfill branch and start no web server. | The CLI checks --backfill before single-run and aggregate handling. | No | | --watch with --web | The browser polls; --watch does not select terminal rendering. The normalized --interval value controls browser polling. | The web page replaces terminal watch output for this invocation. | No | | Active profile missing | Surface the existing SetupUsageError and do not silently switch to a global target. | This preserves resolveSetupTarget behavior. | No | @@ -135,11 +136,10 @@ Setup and usage analytics currently require the terminal, while review already s 5. WHEN the selection contains an orchestrator mode THEN the planner SHALL put its parameter values in the proposed config without persisting a preset label. WEB-16 6. WHEN the selection contains a sandbox value THEN the planner SHALL set defaultSandbox to workspace-write or danger-full-access as selected. WEB-17 7. WHEN the selection turns autocompact on THEN the planner SHALL set autocompact.enabled to true and preserve other autocompact fields. WEB-18 -8. WHEN a setup response is assembled THEN it SHALL expose the exact top-level fields proposta, validacoes, mudancas, and resultado from SetupEnvelope. WEB-20 -9. WHEN setup receives an explicit --profile target THEN the planner SHALL update only that profile snapshot. WEB-19 -10. WHEN setup has no explicit --profile and an existing active profile THEN the planner SHALL use the resolved active profile snapshot. WEB-62 -11. WHEN the terminal wizard completes selections THEN it SHALL use the shared planner without adding catalog validation to the wizard path. WEB-22 -12. WHEN the selection turns autocompact off THEN the planner SHALL set enabled to false if the target config has an autocompact block and SHALL preserve the absent block otherwise. WEB-61 +8. WHEN setup receives an explicit --profile target THEN the planner SHALL update only that profile snapshot. WEB-19 +9. WHEN setup has no explicit --profile and an existing active profile THEN the planner SHALL use the resolved active profile snapshot. WEB-62 +10. WHEN the terminal wizard completes selections THEN it SHALL use the shared planner without adding catalog validation to the wizard path. WEB-22 +11. WHEN the selection turns autocompact off THEN the planner SHALL set enabled to false if the target config has an autocompact block and SHALL preserve the absent block otherwise. WEB-61 **Independent Test**: Call the planner with global and profile RunAgentConfig values. Assert exact proposal and diff paths for each field, skipped role behavior, and absence of file, network, and catalog-validation calls. Run the existing setup wizard and CLI contract tests unchanged. ### P4: Browser setup @@ -160,7 +160,7 @@ Setup and usage analytics currently require the terminal, while review already s 7. WHEN the browser posts selections to /api/setup/dry-run THEN the handler SHALL return a JSON object with exact top-level fields proposta, validacoes, mudancas, and resultado. WEB-27 8. WHEN the browser posts selections to /api/setup/dry-run THEN the handler SHALL leave the config file unchanged. WEB-28 9. IF the proposal has a non-empty diff and all changed bindings validate THEN apply SHALL save it and return resultado.status=applied with saved=true. WEB-29 -10. IF the proposal diff is empty THEN apply SHALL return resultado.status=unchanged with saved=false and SHALL skip the config write. WEB-30 +10. IF the setup selection omits orchestrator and all other values match the resolved target THEN the planner SHALL preserve the target's orchestrator value or its absence and apply SHALL return resultado.status=unchanged with saved=false without writing config. WEB-30 11. IF a setup POST body is malformed, larger than 64 KiB, or has an invalid shape THEN the handler SHALL return HTTP 400 without saving config. WEB-31 12. IF changed binding validation fails THEN the handler SHALL return HTTP 422, include the existing validation code in resultado.code, and set saved=false. WEB-32 13. IF saving the config fails THEN the handler SHALL return HTTP 500 with saved=false and the config error message. WEB-33 @@ -172,7 +172,7 @@ Setup and usage analytics currently require the terminal, while review already s 19. WHEN the setup page is rendered THEN it SHALL offer harness and model choices for every role in ROLES. WEB-37 20. WHEN the user enters harness:model text absent from the displayed catalog THEN the page SHALL allow that value in the selection and dry-run proposal. WEB-64 21. IF an existing binding's harness and model are unchanged from the resolved target THEN web apply SHALL preserve it without catalog validation, even when it is off-catalog. WEB-85 -22. WHEN a binding's harness or model differs from the resolved target THEN web apply SHALL validate that binding and SHALL NOT validate bindings changed only by effort. WEB-21 +22. WHEN a binding's harness or model differs from the resolved target THEN web apply SHALL validate that binding against getBatchModels with allowNetwork:false and SHALL NOT validate bindings changed only by effort. WEB-21 23. WHEN a role is skipped THEN setup SHALL keep its target binding unchanged or leave it unset when no binding exists. WEB-65 24. WHEN all first-run role screens are skipped and no bindings exist THEN apply SHALL write the empty agents object sentinel. WEB-66 25. WHEN the user skips an effort screen for an unchanged harness and model THEN the page logic SHALL preserve that binding's current effort. WEB-67 @@ -183,16 +183,24 @@ Setup and usage analytics currently require the terminal, while review already s 30. WHEN the setup page is rendered THEN it SHALL offer autocompact on and off. WEB-41 31. WHEN setup starts with --profile THEN the state response SHALL identify that profile and apply its proposal only to that profile. WEB-42 32. IF model discovery is incomplete or any requested harness returns an error THEN the refresh response SHALL return getBatchModels cache fallback and discoveryError without partial network results. WEB-59 -33. IF codedeck setup runs without batch flags and either stdin or stdout is not a TTY THEN it SHALL exit with code 1, print “setup needs a terminal on both stdin and stdout”, and start no server. WEB-69 +33. IF codedeck setup runs without batch flags and either stdin or stdout is not a TTY THEN it SHALL exit with code 1, print `${getCliName()} setup needs a terminal on both stdin and stdout.`, and start no server. WEB-69 34. IF codedeck setup receives both --json and --port THEN it SHALL report a setup usage error and start no server. WEB-70 35. WHEN setup guidance is updated THEN README.md lines 154 and 186 SHALL describe browser setup as the default, --tui as the picker entry, and --refresh as the catalog refresh option. WEB-82 -**Independent Test**: Request setup state and catalog, refresh, submit dry-run and apply, and assert exact envelope fields and writes. Cover changed and unchanged off-catalog bindings, profile targets, all role and effort skips, typed models, custom numeric parallelism, invalid config codes, and non-TTY command behavior. +36. IF a changed binding's model is absent from the cached catalog THEN web apply SHALL return HTTP 422 with saved=false unless the request includes offCatalogConfirmed[role]=true for that binding's role. WEB-88 +37. IF a changed binding's model is absent from the cached catalog and the request includes offCatalogConfirmed[role]=true THEN web apply SHALL save that binding and return resultado.status=applied with saved=true. WEB-89 +38. IF catalog refresh returns status=unavailable THEN setup page logic SHALL retain the previously loaded catalog and show the response's discoveryError. WEB-90 +39. IF dry-run encounters invalid config JSON or a config read error THEN the handler SHALL return an error SetupEnvelope with resultado.code=14 for invalid JSON or 15 for a read error, and saved=false. WEB-91 +40. IF GET /api/setup/state encounters invalid config JSON or a config read error THEN the handler SHALL return a JSON error with code=14 for invalid JSON or code=15 for a read error. WEB-92 +41. IF apply cannot resolve the active profile because its saved snapshot is missing THEN it SHALL return resultado.code=14 with saved=false and SHALL NOT write config. WEB-93 +42. IF codedeck setup receives --port with --dry-run or --non-interactive THEN it SHALL report a setup usage error and start no server. WEB-94 +43. WHEN a setup response is assembled THEN it SHALL expose the exact top-level fields proposta, validacoes, mudancas, and resultado from SetupEnvelope. WEB-20 +**Independent Test**: Request setup state and catalog, refresh, submit dry-run and apply, and assert exact envelope fields and writes. Cover off-catalog apply with and without per-role confirmation, refresh failure with a previously loaded catalog, profile targets, all role and effort skips, typed models, custom numeric parallelism, invalid config and read-error codes on state and dry-run, missing active-profile apply, and conflicting port flags. ### P5: Browser usage analytics **User Story**: As a user reviewing agent costs, I want a browser dashboard with the CLI filters and available breakdowns so that I can inspect usage without the terminal. -**Why P5**: The usage web must build on the merged orchestrator-usage contracts and retain compatibility with older daemons. +**Why P5**: Usage origin support is already in main at 80ec486 (#103); the page must retain compatibility with an older running daemon. **Acceptance Criteria**: @@ -205,18 +213,19 @@ Setup and usage analytics currently require the terminal, while review already s 6. WHEN a usage query succeeds THEN the page logic SHALL expose byModel buckets for rendering. WEB-48 7. WHEN a usage query succeeds THEN the page logic SHALL expose byAgent buckets for rendering. WEB-49 8. WHEN a usage query succeeds THEN the page logic SHALL expose byRun buckets for rendering. WEB-50 -9. WHEN a post-merge result contains byOrigin THEN the page logic SHALL expose its buckets for rendering. WEB-51 -10. IF a result returned by an older daemon has no byOrigin field THEN the page logic SHALL render the other breakdowns without an error. WEB-52 +9. WHEN a usage result contains byOrigin THEN the page logic SHALL expose its buckets for rendering. WEB-51 +10. IF a result from an older running daemon over IPC has no byOrigin field THEN the page logic SHALL render the other breakdowns without an error. WEB-52 11. WHILE the usage page is open THEN its state logic SHALL poll the active query using the normalized interval value. WEB-53 12. WHEN codedeck usage runs with --web and no run ID THEN it SHALL open /usage with aggregate filters and the selected --by breakdown. WEB-54 -13. WHEN codedeck usage runs without --web THEN it SHALL preserve the snapshot, TUI, watch, plain, JSON, --by origin, --observe, and --backfill contracts from the CLI and feat/orchestrator-usage. WEB-55 +13. WHEN codedeck usage runs without --web THEN it SHALL preserve the snapshot, TUI, watch, plain, JSON, --by origin, --observe, and --backfill contracts from the CLI. WEB-55 14. IF --backfill is absent and codedeck usage receives a positional run ID or --run THEN it SHALL call usage.get, preserve valid --observe data and --json output, ignore aggregate-only --by origin, and start no web server even if --web is present. WEB-56 15. IF codedeck usage receives --backfill together with --web THEN it SHALL run backfill and start no web server. WEB-83 16. WHEN usage web polling normalizes --interval with Math.max(1, Number(opts.interval) || 2) THEN zero and non-numeric values SHALL resolve to 2 seconds and negative or positive values below 1 SHALL resolve to 1 second. WEB-81 17. WHEN a usage page filter for period, repo, model, agent, since, or until changes THEN the page logic SHALL issue a new query with the updated filters. WEB-78 18. IF a usage query fails after a successful result THEN the page logic SHALL show the error and retain the last successful result. WEB-60 19. WHEN codedeck usage receives --by origin THEN the page logic SHALL select origin as the initial breakdown while leaving all available sections reachable. WEB-80 -**Independent Test**: Use fixed options, cwd, and time to assert CLI and web query parameter parity. Test the page logic directly in Node for filters, polling, query errors, and results with and without byOrigin. Test CLI runs with --web, --by origin, --backfill, --observe, --json, and a single-run ID without starting an unintended server. +20. IF codedeck usage receives --web and --tui THEN it SHALL report a usage error and start no web server. WEB-95 +**Independent Test**: Use fixed options, cwd, and time to assert CLI and /api/usage query parameter equality. Test the page logic directly in Node for filters, polling, query errors, and results with and without byOrigin. Test CLI runs with --web, --web --tui, --by origin, --backfill, --observe, --json, and a single-run ID without starting an unintended server. ## Edge Cases @@ -255,7 +264,7 @@ Each acceptance criterion has one requirement ID and maps to the task that imple | WEB-17 | P3: Shared setup planning | P3 | In Tasks | T8 | | WEB-18 | P3: Shared setup planning | P3 | In Tasks | T8 | | WEB-19 | P3: Shared setup planning | P3 | In Tasks | T8 | -| WEB-20 | P3: Shared setup planning | P3 | In Tasks | T11 | +| WEB-20 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-21 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-22 | P3: Shared setup planning | P3 | In Tasks | T9 | | WEB-23 | P4: Browser setup | P4 | In Tasks | T10 | @@ -265,7 +274,7 @@ Each acceptance criterion has one requirement ID and maps to the task that imple | WEB-27 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-28 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-29 | P4: Browser setup | P4 | In Tasks | T11 | -| WEB-30 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-30 | P4: Browser setup | P4 | In Tasks | T8, T11 | | WEB-31 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-32 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-33 | P4: Browser setup | P4 | In Tasks | T11 | @@ -307,7 +316,7 @@ Each acceptance criterion has one requirement ID and maps to the task that imple | WEB-70 | P4: Browser setup | P4 | In Tasks | T12 | | WEB-71 | P2: Local request security | P2 | In Tasks | T6, T7 | | WEB-72 | P2: Local request security | P2 | In Tasks | T6, T7 | -| WEB-73 | P2: Local request security | P2 | In Tasks | T7, T10 | +| WEB-73 | P2: Local request security | P2 | In Tasks | T10 | | WEB-74 | P2: Local request security | P2 | In Tasks | T6, T7 | | WEB-75 | P1: Shared local server and home page | P1 | In Tasks | T1, T7 | | WEB-76 | P4: Browser setup | P4 | In Tasks | T11 | @@ -321,8 +330,16 @@ Each acceptance criterion has one requirement ID and maps to the task that imple | WEB-85 | P4: Browser setup | P4 | In Tasks | T11 | | WEB-86 | P3: Shared setup planning | P3 | In Tasks | T8 | | WEB-87 | P3: Shared setup planning | P3 | In Tasks | T8 | - -**Coverage**: 85 total requirements, 85 mapped to tasks, 0 unmapped. +| WEB-88 | P4: Browser setup | P4 | In Tasks | T10, T11 | +| WEB-89 | P4: Browser setup | P4 | In Tasks | T10, T11 | +| WEB-90 | P4: Browser setup | P4 | In Tasks | T10, T11 | +| WEB-91 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-92 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-93 | P4: Browser setup | P4 | In Tasks | T11 | +| WEB-94 | P4: Browser setup | P4 | In Tasks | T12 | +| WEB-95 | P5: Browser usage analytics | P5 | In Tasks | T17 | + +**Coverage**: 93 total requirements, 93 mapped to tasks, 0 unmapped. ## External Dependencies None. The feature adds no external-system integration; catalog access reuses the repository's existing model-discovery code. @@ -335,4 +352,4 @@ None. The feature adds no external-system integration; catalog access reuses the - [ ] Setup state pre-fills the resolved profile or global target, and dry-run never writes config. - [ ] Web apply validates only changed harness:model bindings and saves no invalid proposal. - [ ] Existing setup wizard, setup CLI contract, review, usage CLI, JSON, and statusline tests pass unchanged. -- [ ] After the orchestrator-usage commit is an ancestor of implementation HEAD, usage query params match between CLI and web and page logic passes Node tests without a DOM dependency. +- [ ] Usage query params match between CLI and /api/usage for equal inputs, and page logic passes Node tests without a DOM dependency. diff --git a/.specs/features/web-console/tasks.md b/.specs/features/web-console/tasks.md index 2ce3f86..7847f5e 100644 --- a/.specs/features/web-console/tasks.md +++ b/.specs/features/web-console/tasks.md @@ -16,11 +16,11 @@ Implement these tasks with the tlc-spec-driven skill. Keep tests in the task tha | --- | --- | --- | --- | --- | | Web server/router | Integration | Loopback bind, actual port after listen, port parsing, route dispatch, review aliases, browser URL, listen failure, and injected shutdown | tests/web-server.test.ts, tests/review.test.ts, tests/review-command.test.ts | npx vitest run tests/web-server.test.ts tests/review.test.ts tests/review-command.test.ts | | Security | Integration | Allowed and rejected Host, token bootstrap and cookie, Origin, POST rejection before dispatch, and HTML framing header | tests/web-security.test.ts, tests/web-server.test.ts | npx vitest run tests/web-security.test.ts tests/web-server.test.ts | -| Setup core | Unit and integration | Planner fields and preservation, profile targets, skip behavior, no catalog validation in planner, separate changed-binding validation, wizard and batch compatibility | tests/setup-plan.test.ts, tests/setup-wizard.test.ts, tests/setup-cli-contract.test.ts | npx vitest run tests/setup-plan.test.ts tests/setup-wizard.test.ts tests/setup-cli-contract.test.ts | -| Setup web handlers | Integration | State prefill, catalog cache and refresh fallback, dry-run no-write, apply, changed-only validation, invalid config codes, malformed body, and save errors | tests/setup-web.test.ts | npx vitest run tests/setup-web.test.ts | -| Usage query builder | Unit and command contract | CLI and web callers produce identical UsageQueryParams for the same options, cwd, and clock; all supported filters and precedence | tests/usage-cli.test.ts | npx vitest run tests/usage-cli.test.ts | -| Usage web handler | Integration | Query mapping, every supported filter, query success, and query error response | tests/usage-web.test.ts | npx vitest run tests/usage-web.test.ts | -| HTML pages and page behavior | Unit | Direct Node tests of injected page functions for setup state, free-text input, discovery, 403 message, filters, polling, rendering data, optional byOrigin, and retained result on error | tests/web-pages.test.ts | npx vitest run tests/web-pages.test.ts | +| Setup core | Unit and integration | Planner fields and preservation, optional orchestrator, profile targets, skip behavior, no catalog validation in planner, config-owned shared helpers, separate changed-binding validation, wizard and batch compatibility | tests/setup-plan.test.ts, tests/setup-wizard.test.ts, tests/setup-cli-contract.test.ts | npx vitest run tests/setup-plan.test.ts tests/setup-wizard.test.ts tests/setup-cli-contract.test.ts | +| Setup web handlers | Integration | State prefill and error codes, catalog cache and refresh fallback, dry-run no-write, apply, changed-only validation with per-role off-catalog confirmation, missing profile, malformed body, and save errors | tests/setup-web.test.ts | npx vitest run tests/setup-web.test.ts | +| Usage query builder | Unit and command contract | Existing CLI filter precedence and mappings, with fixed options, cwd, and clock | tests/usage-cli.test.ts | npx vitest run tests/usage-cli.test.ts | +| Usage web handler | Integration | Query mapping, every supported filter, CLI versus /api/usage parameter equality, query success, and query error response | tests/usage-web.test.ts | npx vitest run tests/usage-web.test.ts | +| HTML pages and page behavior | Unit | Direct Node tests of injected page functions plus node:vm execution of extracted SETUP_PAGE and USAGE_PAGE scripts with stubbed browser globals; setup state, free-text input, discovery errors, 403 message, filters, polling, rendering data, optional byOrigin, and retained result on error | tests/web-pages.test.ts | npx vitest run tests/web-pages.test.ts | | CLI wiring | Command contract | ui, review, setup, and usage routes and flags; setup batch and non-TTY behavior; usage.get with --web --json without server startup | tests/web-cli.test.ts, tests/review-command.test.ts, tests/setup-cli-contract.test.ts, tests/usage-cli.test.ts, tests/usage-statusline-contract.test.ts | npx vitest run tests/web-cli.test.ts tests/review-command.test.ts tests/setup-cli-contract.test.ts tests/usage-cli.test.ts tests/usage-statusline-contract.test.ts | | Documentation | none | Text-only README update; no test coverage required | None | git diff --check -- README.md | @@ -33,11 +33,11 @@ Implement these tasks with the tlc-spec-driven skill. Keep tests in the task tha | P2 | After security integration | npx vitest run tests/web-security.test.ts tests/web-server.test.ts tests/web-pages.test.ts | | P3 | After setup core extraction | npx vitest run tests/setup-plan.test.ts tests/setup-wizard.test.ts tests/setup-cli-contract.test.ts | | P4 | After setup web wiring | npx vitest run tests/setup-web.test.ts tests/web-pages.test.ts tests/web-cli.test.ts tests/setup-cli-contract.test.ts | -| P5 | After the usage changes land | git merge-base --is-ancestor origin/feat/orchestrator-usage HEAD && npx vitest run tests/usage-cli.test.ts tests/usage-web.test.ts tests/web-pages.test.ts tests/web-cli.test.ts tests/usage-statusline-contract.test.ts | +| P5 | After P4 | npx vitest run tests/usage-cli.test.ts tests/usage-web.test.ts tests/web-pages.test.ts tests/web-cli.test.ts tests/usage-statusline-contract.test.ts | ## Execution Plan -Phases run in dependency order. Within each phase, start a task after its listed dependencies pass. P5 cannot start until P4 is complete and origin/feat/orchestrator-usage is an ancestor of implementation HEAD. +Phases run in dependency order. Within each phase, start a task after its listed dependencies pass. P5 starts after P4. Usage origin support is already in main at 80ec486 (#103). ### Phase 1: Shared server and home page @@ -209,7 +209,7 @@ T16 -> T18 **Where**: src/web/server.ts **Depends on**: T1, T6 **Reuses**: Route policies from src/web/security.ts -**Requirement**: WEB-10, WEB-12, WEB-13, WEB-71, WEB-73, WEB-74, WEB-75 +**Requirement**: WEB-10, WEB-12, WEB-13, WEB-71, WEB-74, WEB-75 **Done when**: @@ -222,21 +222,24 @@ T16 -> T18 **Tests**: Integration, tests/web-security.test.ts and tests/web-server.test.ts **Gate**: npx vitest run tests/web-security.test.ts tests/web-server.test.ts -### T8: Add the pure setup planner and separate binding validator +### T8: Extract shared setup logic into the config layer -**What**: Add a pure planner that applies setup selections to RunAgentConfig and a separate binding validation function used by callers. -**Where**: src/config/setup-plan.ts +**What**: Move shared setup planning, target resolution, diffing, envelope types, and binding validation from the CLI command into the config layer. +**Where**: src/config/setup.ts **Depends on**: T7 -**Reuses**: RunAgentConfig, profile helpers, diffConfig, role bindings, and existing catalog validation rules -**Requirement**: WEB-14, WEB-15, WEB-16, WEB-17, WEB-18, WEB-19, WEB-61, WEB-62, WEB-86, WEB-87 +**Reuses**: RunAgentConfig, profile helpers, role bindings, and existing catalog validation rules +**Requirement**: WEB-14, WEB-15, WEB-16, WEB-17, WEB-18, WEB-19, WEB-30, WEB-61, WEB-62, WEB-86, WEB-87 **Done when**: - buildSetupPlan accepts RunAgentConfig, a resolved target, and selections; it returns proposedConfig and diff only. +- resolveSetupTarget, diffConfig, SetupEnvelope, catalogContains, and validateBindings are exported from src/config/setup.ts for CLI and web callers. +- No src/config module imports from src/cli. - The planner does not read or write files, discover models, or call catalog validation. -- Separate binding validation can be invoked by web apply and batch without being called by the planner. +- Separate binding validation can be invoked by web apply and batch without being called by the planner; the wizard path still adds no catalog validation. +- Planner tests omit orchestrator and assert preservation of both an existing value and its absence. - Planner tests cover selected values, skipped roles, unrelated config keys, profiles, and diff paths for each setup field. -- Planner tests prove typed or off-catalog binding values can be planned without catalog validation. +- Tests cover off-catalog selections in the planner, profile resolution, diffing, and separate binding validation. **Tests**: Unit, tests/setup-plan.test.ts **Gate**: npx vitest run tests/setup-plan.test.ts @@ -265,17 +268,21 @@ T16 -> T18 **Where**: src/web/setup-page.ts **Depends on**: T8 **Reuses**: Wizard selection semantics and the route contracts in spec.md -**Requirement**: WEB-23, WEB-25, WEB-37, WEB-38, WEB-39, WEB-40, WEB-41, WEB-64, WEB-65, WEB-66, WEB-67, WEB-68, WEB-73 +**Requirement**: WEB-23, WEB-25, WEB-37, WEB-38, WEB-39, WEB-40, WEB-41, WEB-64, WEB-65, WEB-66, WEB-67, WEB-68, WEB-73, WEB-88, WEB-89, WEB-90 **Done when**: - The page has a free-text harness:model field for every role, with supported effort choices and no effort control for opencode. - It has role skip controls, orchestrator presets and custom parameters, both sandbox values, and autocompact on/off. +- It asks for explicit per-role confirmation before sending an off-catalog model in offCatalogConfirmed. - Its state logic preserves skipped roles and effort; a first-run all-role skip can produce agents: {}. - Positive finite custom parallelism is kept as a number in the proposal. - A pending refresh exposes “Discovering models...” until the response completes. +- An unavailable refresh keeps the previously loaded catalog and shows discoveryError. +- An untouched selection can omit orchestrator and preserves the current value or its absence. - A protected POST response of 403 exposes the exact reload/restart message from WEB-73. - The HTML injects its exported behavior function source; Node tests call those same functions with fake fetch and state callbacks without a DOM. +- Tests extract the inline script from SETUP_PAGE, evaluate it in node:vm with an empty context and stubbed fetch, timers, and document, then call the setup page functions from that context. **Tests**: Unit, tests/web-pages.test.ts **Gate**: npx vitest run tests/web-pages.test.ts @@ -286,20 +293,23 @@ T16 -> T18 **Where**: src/web/setup-routes.ts **Depends on**: T7, T8 **Reuses**: readConfigForSetup, resolveSetupTarget, getBatchModels, buildSetupPlan, and separate binding validation -**Requirement**: WEB-20, WEB-21, WEB-24, WEB-26, WEB-27, WEB-28, WEB-29, WEB-30, WEB-31, WEB-32, WEB-33, WEB-59, WEB-63, WEB-76, WEB-77, WEB-84, WEB-85 +**Requirement**: WEB-20, WEB-21, WEB-24, WEB-26, WEB-27, WEB-28, WEB-29, WEB-30, WEB-31, WEB-32, WEB-33, WEB-59, WEB-63, WEB-76, WEB-77, WEB-84, WEB-85, WEB-88, WEB-89, WEB-90, WEB-91, WEB-92, WEB-93 **Done when**: -- GET /api/setup/state returns the global or named profile target and current bindings, effort, orchestrator, sandbox, and autocompact values for prefill. +- GET /api/setup/state returns the global or named profile target and current bindings, effort, optional orchestrator, sandbox, and autocompact values for prefill; invalid JSON returns code 14 and a read error returns code 15. - An active profile without a snapshot returns the existing SetupUsageError instead of selecting global config. +- Apply returns code 14 and saved=false without a write when the active profile has no saved snapshot. - GET /api/setup/catalog calls only getBatchModels with allowNetwork:false and returns its actual result fields. -- Protected refresh calls getBatchModels with refresh:true, allowNetwork:true, and timeoutMs:12000; incomplete discovery returns the helper's cache fallback and discoveryError without partial network results. +- Protected refresh calls getBatchModels with refresh:true, allowNetwork:true, and timeoutMs:12000; incomplete discovery returns the helper's refresh fallback and discoveryError without partial network results. - Concurrent refresh callers share the in-flight promise. - Dry-run returns an exact SetupEnvelope with proposta, validacoes, mudancas, and resultado and does not write config. -- Apply uses readConfigForSetup, validates only bindings whose harness or model changed, does not validate effort-only changes, and does not block unrelated changes for an unchanged off-catalog binding. -- Invalid JSON returns resultado.code=14 and a read error returns resultado.code=15; both paths do not write config. +- Apply uses readConfigForSetup, validates only changed harness:model pairs with getBatchModels allowNetwork:false, does not validate effort-only changes, and does not block unrelated changes for an unchanged off-catalog binding. +- An off-catalog changed model returns HTTP 422 unless its role has offCatalogConfirmed=true in the request; a confirmed off-catalog model saves. +- Dry-run returns resultado.code=14 for invalid JSON and resultado.code=15 for a config read error; neither path writes config. +- GET /api/setup/state returns JSON code 14 for invalid JSON and code 15 for a config read error. - Empty diffs return unchanged and do not write; valid non-empty diffs save; malformed bodies return 400; validation failures return 422; save failures return 500. -- Tests cover state, cache and refresh results, concurrency, exact envelope keys, changed and unchanged off-catalog bindings, invalid/read errors, and every no-write path. +- Tests cover state, cache and refresh results, concurrency, exact envelope keys, changed and unchanged off-catalog bindings with and without confirmation, invalid/read errors, missing active profiles, and every no-write path. **Tests**: Integration, tests/setup-web.test.ts **Gate**: npx vitest run tests/setup-web.test.ts @@ -310,7 +320,7 @@ T16 -> T18 **Where**: src/cli/commands/setup.ts **Depends on**: T9, T10, T11 **Reuses**: Existing argument parser and runSetupBatch -**Requirement**: WEB-09, WEB-34, WEB-35, WEB-36, WEB-42, WEB-69, WEB-70 +**Requirement**: WEB-09, WEB-34, WEB-35, WEB-36, WEB-42, WEB-69, WEB-70, WEB-94 **Done when**: @@ -320,7 +330,9 @@ T16 -> T18 - Batch flags continue to call runSetupBatch without changing JSON, dry-run, bind, validation, save, or exit-code contracts. - --profile targets the selected profile and --refresh starts the protected catalog refresh after /setup loads. - --json with --port returns a usage error before server startup. +- --dry-run with --port and --non-interactive with --port return usage errors before server startup. - --port, --no-open, open failure, and printed token URL are covered by web command tests. +- Tests assert --json, --dry-run, and --non-interactive each reject --port without starting a server. - Existing setup CLI contract tests pass without changes. **Tests**: Command contract, tests/web-cli.test.ts and tests/setup-cli-contract.test.ts @@ -365,19 +377,17 @@ T16 -> T18 **What**: Extract buildUsageQueryParams(opts, cwd, now) from the aggregate CLI branch into the shared helper and use it from src/cli/commands/usage.ts. **Where**: src/core/usage-query.ts **Depends on**: None -**Reuses**: Existing aggregate filter precedence in src/cli/commands/usage.ts:129-164 +**Reuses**: Existing aggregate filter precedence in src/cli/commands/usage.ts:175-211 **Requirement**: WEB-44 **Done when**: - The pure function preserves all, today, days, default-today, current-over-repo, since, until, model, and agent behavior. - src/cli/commands/usage.ts calls the shared function with parsed options, process.cwd(), and the current date. -- The same options, cwd, and now produce identical UsageQueryParams for CLI and web callers. -- tests/usage-cli.test.ts covers the helper and CLI parity cases. -- Before P5 starts, origin/feat/orchestrator-usage is an ancestor of HEAD. +- tests/usage-cli.test.ts covers the helper and existing CLI query contract. **Tests**: Unit and command contract, tests/usage-cli.test.ts -**Gate**: git merge-base --is-ancestor origin/feat/orchestrator-usage HEAD && npx vitest run tests/usage-cli.test.ts +**Gate**: npx vitest run tests/usage-cli.test.ts ### T15: Add the usage query route @@ -391,6 +401,7 @@ T16 -> T18 - Query parsing maps period, repo, model, agent, since, until, all, today, days, and current to the shared builder input. - --current resolves to the CLI process working directory and overrides repo. +- For identical parsed CLI options, cwd, and clock, tests compare the CLI UsageQueryParams with the parameters passed by GET /api/usage. - Successful results retain the UsageQueryResult shape. - Query errors return an HTTP 500 JSON error. - tests/usage-web.test.ts covers each filter, the current override, success, and error response. @@ -409,23 +420,24 @@ T16 -> T18 **Done when**: - Page logic exposes every UsageTotals field and each of byDay, byRepository, byModel, byAgent, and byRun. -- It exposes byOrigin when present and handles its absence from an older daemon without throwing. +- It exposes byOrigin when present and handles its absence from an older running daemon process over IPC without throwing. - Period, repo, model, agent, since, and until changes each issue a query with the updated filters. - Polling refreshes the active filter set and normalizes the interval with Math.max(1, Number(opts.interval) || 2). - A query error leaves the last successful result visible and stores the error state. - --by origin selects origin initially while every available breakdown remains reachable. - The HTML injects the exported behavior function source; tests directly call that function in Node with fake fetch, timer, and render adapters. +- Tests extract the inline script from USAGE_PAGE, evaluate it in node:vm with an empty context and stubbed fetch, timers, and document, then call the usage page functions from that context. **Tests**: Unit, tests/web-pages.test.ts **Gate**: npx vitest run tests/web-pages.test.ts ### T17: Add usage --web command wiring -**What**: Add aggregate usage web startup while keeping single-run and post-merge CLI branches ahead of it. +**What**: Add aggregate usage web startup while keeping the existing single-run and aggregate CLI branches in order. **Where**: src/cli/commands/usage.ts **Depends on**: T14, T15, T16 **Reuses**: Shared server, usage route factory, and existing command options -**Requirement**: WEB-09, WEB-54, WEB-55, WEB-56, WEB-80, WEB-81, WEB-83 +**Requirement**: WEB-09, WEB-54, WEB-55, WEB-56, WEB-80, WEB-81, WEB-83, WEB-95 **Done when**: @@ -434,6 +446,8 @@ T16 -> T18 - --web --json for aggregate usage starts the page and prints its token URL. - When --backfill is absent, positional ID or --run takes the usage.get path before web startup, preserves --observe and --json, ignores aggregate-only --by origin, and starts no server. - --backfill runs before web startup and starts no server even with --web. +- --web with --tui returns a usage error and starts no server. +- tests/web-cli.test.ts asserts --web --tui returns a usage error without starting a server. - Without --web, snapshot, TUI, watch, plain, JSON, --by origin, --observe, and --backfill contracts remain unchanged. - tests/web-cli.test.ts proves usage --web --json calls usage.get and starts no server. - tests/usage-cli.test.ts covers --by origin, --observe, --backfill, and --interval contracts.