Damascus Entry Points P5: damascus-ui v2 (ingest + 4 widgets + project-grouped dashboard) #19

Closed
kaykayyali wants to merge 0 commits from feat/entry-points-ui-v2 into main
Owner

P5 — damascus-ui v2

Closes the UI v2 contract from wiki/concepts/entry-points-contract.md §8.

What's in this PR

UI v2 (P5 deliverables)

  • `/ingest` route with full Ingest form (project, story_id, title, file_scope, priority, budget_cycles) — Pydantic-validated client-side
  • ItemDrawer answer form, only enabled when item is `awaiting_human` and has open issues
  • Project-grouped Dashboard: one tab per project, four self-improving widgets per project
  • Four widgets: PhaseBar (extracted from v1 Dashboard), OpenIssues (count + last 5 clickable), BlockedItems (verdict + feedback cards), CostSparkline (inline SVG, no X-Charts dep)
  • React Query hooks for ingest, answer, cost, grouped, open issues
  • API client sends `Authorization` on writes; reads remain anonymous
  • Pydantic mirrors for P5 (IngestStoryRequest, AnswerIssueRequest, CostSummaryResponse, GroupedItemsResponse, group_by param)
  • Vitest config; fixture API port moved to :9111 so it doesn't collide with live damascus-api :9110

API wiring (gap fix)

  • `?group_by=project` on `GET /v1/items` now wired in the runtime handler. Schema landed in an earlier commit but the handler never picked it up — without this, every dashboard load would 422.
  • response_model widened to `Union[ListItemsResponse, GroupedItemsResponse]` for OpenAPI parity

Tests

  • 28/28 UI unit tests pass (8 test files; new P5 routes/widget tests)
  • 34/34 API endpoint tests pass (4 new for group_by: shape, filter interaction, 400, no-group_by regression)
  • 86/86 across tests/api + tests/contract
  • Live verified: 2 items posted → `?group_by=project` returns single bucket with both items + per-phase counts
  • Playwright e2e (`test_ui_v2.spec.ts`) covers ingest redirect, dashboard widgets, project-grouped tabs, answer form, mobile viewport (375x667)

Build

  • `npm run build` → 1517 modules, `dist/assets/index-D8N6FRSv.js` 968 KB (size warning is informational)

Decisions

  • CostSparkline uses inline SVG to avoid pulling in @mui/x-charts
  • ItemDrawer answer form only enables when `item.status === "awaiting_human"`
  • Grouped view does not honor sort/pagination — dashboard renders full set, pagination lives on the flat `/v1/items` endpoint
  • Container rebuild required for the API change: `docker compose build damascus-api && docker compose up -d --force-recreate damascus-api`

Followups (NOT in this PR)

  • P6 (E2E verify) — dispatches MCP-driven live cycle + UI screenshot per contract §8
  • Deferred-but-cheap (per §7): operator-note textarea on blocked items → events_outbox
## P5 — damascus-ui v2 Closes the UI v2 contract from `wiki/concepts/entry-points-contract.md` §8. ### What's in this PR **UI v2 (P5 deliverables)** - \`/ingest\` route with full Ingest form (project, story_id, title, file_scope, priority, budget_cycles) — Pydantic-validated client-side - ItemDrawer answer form, only enabled when item is \`awaiting_human\` and has open issues - Project-grouped Dashboard: one tab per project, four self-improving widgets per project - Four widgets: **PhaseBar** (extracted from v1 Dashboard), **OpenIssues** (count + last 5 clickable), **BlockedItems** (verdict + feedback cards), **CostSparkline** (inline SVG, no X-Charts dep) - React Query hooks for ingest, answer, cost, grouped, open issues - API client sends \`Authorization\` on writes; reads remain anonymous - Pydantic mirrors for P5 (IngestStoryRequest, AnswerIssueRequest, CostSummaryResponse, GroupedItemsResponse, group_by param) - Vitest config; fixture API port moved to :9111 so it doesn't collide with live damascus-api :9110 **API wiring (gap fix)** - \`?group_by=project\` on \`GET /v1/items\` now wired in the runtime handler. Schema landed in an earlier commit but the handler never picked it up — without this, every dashboard load would 422. - response_model widened to \`Union[ListItemsResponse, GroupedItemsResponse]\` for OpenAPI parity ### Tests - 28/28 UI unit tests pass (8 test files; new P5 routes/widget tests) - 34/34 API endpoint tests pass (4 new for group_by: shape, filter interaction, 400, no-group_by regression) - 86/86 across tests/api + tests/contract - Live verified: 2 items posted → \`?group_by=project\` returns single bucket with both items + per-phase counts - Playwright e2e (\`test_ui_v2.spec.ts\`) covers ingest redirect, dashboard widgets, project-grouped tabs, answer form, mobile viewport (375x667) ### Build - \`npm run build\` → 1517 modules, \`dist/assets/index-D8N6FRSv.js\` 968 KB (size warning is informational) ### Decisions - CostSparkline uses inline SVG to avoid pulling in @mui/x-charts - ItemDrawer answer form only enables when \`item.status === "awaiting_human"\` - Grouped view does not honor sort/pagination — dashboard renders full set, pagination lives on the flat \`/v1/items\` endpoint - Container rebuild required for the API change: \`docker compose build damascus-api && docker compose up -d --force-recreate damascus-api\` ### Followups (NOT in this PR) - P6 (E2E verify) — dispatches MCP-driven live cycle + UI screenshot per contract §8 - Deferred-but-cheap (per §7): operator-note textarea on blocked items → events_outbox
kaykayyali added 15 commits 2026-06-25 05:09:44 +00:00
The v1 e2e suite (npm run test:e2e) hardcoded port 9110 for the
fixture_api.py and VITE_API_BASE_URL. P2's real damascus-api now binds
9110 on the developer host, so reuseExistingServer: true makes the
suite hit the real (empty) API and the tests fail with '0 matching'.
Move the fixture to 9111 by default; CI / clean hosts override with
FIXTURE_API_PORT=9110.

Also adds docs/plans/2026-06-24-p5-damascus-ui-v2.md (the P5 plan
that the worker will execute against), a test:unit script, and the
testing-library devDeps needed by the v2 component tests.
Adds VITE_API_WRITE_TOKEN env var (baked at build time, LAN-trusted
per contract §4). When non-empty, every POST sets
'Authorization: Bearer ***. GETs remain token-free per the
contract. Empty token (test fixture, read-only deployment) is a
no-op — the bundle still ships and the write fails server-side with
401.

Adds vitest.config.ts + tests/unit/setup.ts + 3 unit tests covering
the header-on-POST / header-absent-on-GET / header-absent-on-empty
paths. TDD: red wrote the tests first, green added the header.
- useIngestStory / useAnswerIssue: useMutation with onSuccess
  invalidation of the relevant query keys (items, stats, item, issues)
- useCostSummary(days): polled every 5s, returns CostSummaryResponse
- useGroupedItems: /v1/items?group_by=project → GroupedItemsResponse
- useOpenIssues(limit): /v1/issues?status=open&limit=N for the OpenIssues widget

Drops dead v1 exports (deriveProjects / matchesPhaseFilter / DEFAULT_*
constants) that had no consumers.
- POST /v1/items (in-memory insert, idempotent on (project, story_id),
  validates IngestStoryRequest field constraints)
- POST /v1/issues/{id}/answer (sets answer/status='answered'/
  answered_at, returns AnswerIssueResponse)
- GET /v1/cost?days=N (synthetic 7-day deterministic data; one spike
  day so the CostSparkline widget has a visible shape)
- GET /v1/issues?status=&project=&limit=&offset= (backing the
  OpenIssues widget's 'last 5' list)
- GET /v1/items?group_by=project returns GroupedItemsResponse
  (GroupedItemsResponse shape matches src/damascus/api_schemas.py
  P5 additions); bad group_by values return 400

Fixture dataset grows from 3 items (v1) to 5 (v2): adds an item in
awaiting_human with an open issue (answer-form target) and a blocked
item with last_verdict+last_feedback (BlockedItems target). Also
adds 2 events for the awaiting_human item so the drawer's recent
events list has something to render.

v1 e2e open-issues-count expectation bumped 1→2 to match the new
fixture.
TDD: red wrote the test, green extracted the component. Pure
presentation: takes phase_counts + total, renders the stacked Paper
+ Box bar. width is set as inline style (not sx) so the test can
read element.style.width directly; sx routes dynamic values through
emotion's stylesheet where assertion is harder. The Dashboard and
any project-grouped sub-view can now mount this in place of the
inline rendering.
Brings the worker-authored Playwright spec into the branch so the PR diff
is complete. Covers: /ingest form redirect, dashboard widget rendering,
project-grouped tabs, ItemDrawer answer form for awaiting_human items,
and a 375x667 mobile-viewport smoke (no fixed pixel widths).

Runs against the fixture_api on :9111 (vite build with
VITE_API_BASE_URL=http://127.0.0.1:9111) seeded with one awaiting_human
item, one open issue, and 7 days of cost data.
feat(api): wire ?group_by=project on /v1/items (P5)
Some checks failed
test / contract-and-unit (pull_request) Failing after 14s
1a0ca369fe
P5 schema (GroupedItemsResponse + ProjectGroup + ListItemsQuery.group_by)
landed in 79d1d74 but the runtime handler never wired it. Without this
commit, the dashboard renders against a 422 on every load.

Handler routing:
  - group_by=project  -> GroupedItemsResponse (one bucket per project,
                          per-phase counts, sort/pagination intentionally
                          not honored in the grouped view)
  - group_by=<other>  -> 400 bad_request
  - group_by absent   -> ListItemsResponse (unchanged)

response_model widened to Union[ListItemsResponse, GroupedItemsResponse]
so FastAPI's OpenAPI schema reflects both shapes.

Tests: 4 new cases covering grouped shape, filter interaction, the 400
path, and a regression check that no-group_by stays flat. 34/34 in
tests/api/test_api_endpoints.py, 86/86 across tests/api + tests/contract.

Live verified: POST 2 items, GET /v1/items?group_by=project returns
single project bucket with both items and per-phase counts.
kaykayyali closed this pull request 2026-06-27 16:42:26 +00:00
Some checks failed
test / contract-and-unit (pull_request) Failing after 14s

Pull request closed

Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kaykayyali/damascus-orchestrator#19