Skip to content

feat: web-first next phases (workspace viewer, jobs at scale, observability, hardening) - #13

Merged
hutusi merged 10 commits into
mainfrom
feat/web-phase-7-10
Jun 20, 2026
Merged

feat: web-first next phases (workspace viewer, jobs at scale, observability, hardening)#13
hutusi merged 10 commits into
mainfrom
feat/web-phase-7-10

Conversation

@hutusi

@hutusi hutusi commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Next web-first phases (7–10)

Builds on the merged web-first platform (#12). Four confirmed tracks; production-path/ops work (graphile-worker runner, real auth, API deploy) is deferred to a later round. Each phase is a thin, end-to-end, web-first slice that reuses existing seams and keeps the hermetic Bun gate green. Opened as a draft early so CI + CodeRabbit run continuously.

Landed so far

  • Phase 7 — Live-run debugging. GET /jobs/:id/workspace (manifest) + /workspace/file (one decoded file; ~256 KB cap; binary → base64) over the checkpoint snapshot the Worker already persists. Console: a Workspace card with a file tree (pure buildFileTree) + lazy viewer via a same-origin proxy route, and a Steps / Logs timeline toggle (Logs = tool output).
  • Phase 8 — Jobs at scale. GET /jobs gains state/q/created_after/created_before/limit/offset (filtered over listByFactio; Postgres push-down noted) → a paged envelope { jobs, total, limit, offset }. Console: filter/search bar + URL-driven pager + inline run/pause/cancel/approve per row, with the state-gating extracted to a shared, tested lib/job-actions.ts.

Coming on this branch

  • Phase 9 — Observability & cost: cost trend + per-model breakdown + quota-utilization bars on the dashboard (CSS/inline-SVG, no chart lib), from existing job records.
  • Phase 10 — Hardening: a hermetic Playwright E2E (stub-runner boots API + Next; login → create → watch live → pause/cancel), an axe/ARIA accessibility baseline, and a role-gated read-only config UI.

Tests

Hermetic Bun (app.test.ts: workspace endpoints, jobs filter/search/pagination/tenant + 400s) and console Vitest (file-tree, job-actions, pager) green; bun run check + console typecheck/test/build all pass. Phase 10 adds a CI e2e job (kept off the Bun gate).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added hermetic Playwright E2E coverage (including accessibility) and CI e2e execution
    • Introduced filterable, paginated Jobs browsing (search/state/date) with inline row actions
    • Added job workspace viewer for checkpoints and file contents (text decode + binary-safe handling)
    • Enhanced dashboard visuals with cost trends, per-model breakdown, and active/quota utilization
    • Added role-based read-only configuration UI and improved run timeline with Steps/Logs tabs
  • Documentation

    • Updated README status through Phase 7 and simplified local development commands
    • Expanded API docs for filtered Jobs and detailed dashboard fields
  • Tests

    • Added unit tests for jobs actions, pagination, file tree, sparkline utilities, and config role gating
    • Updated a PostgreSQL integration test to use polling to avoid timing races

hutusi added 2 commits June 20, 2026 09:07
See the agent's files and output, not just the step timeline.

- api: GET /jobs/:id/workspace (manifest: path+bytes, no base64) and
  /workspace/file?path= (one file, decoded; ~256KB cap with truncated flag;
  binary → base64 passthrough on a NUL byte) over the checkpoint snapshot the
  worker already persists. Tenant-scoped like /trace.
- console: a Workspace card on the job page with a file tree (pure buildFileTree,
  unit-tested) + lazy file viewer via a same-origin proxy route; a Steps/Logs
  toggle on the timeline (Logs = tool output only) via a RunTimeline filter prop.

Hermetic Bun tests cover manifest sort/bytes, utf8/base64 decode, empty/404/400/401.
GET /jobs gains optional state/q/created_after/created_before/limit/offset
(filtered in the API over listByFactio; Postgres push-down noted as a follow-up)
and returns a paged envelope { jobs, total, limit, offset }, newest first.

Console: a filter/search bar + prev/next pager (URL-driven, shareable) above the
jobs table, and inline run/pause/cancel/approve actions on each row. The
state-gating is extracted to a pure, shared lib/job-actions.ts (used by both the
list rows and the detail page) + lib/pager.ts. Updated lib/api.ts + docs/api.md
for the new shape (the console is the only HTTP consumer of /jobs).

Bun tests cover filter/search/date/pagination/tenant-scoping + 400s; Vitest
covers jobActions (gating matrix) and pagerBounds.
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@hutusi, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 43 minutes and 22 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c040a55-7c80-4642-829d-ad88cdfb9c01

📥 Commits

Reviewing files that changed from the base of the PR and between db1fcb8 and cb6cf59.

📒 Files selected for processing (1)
  • packages/habenae/src/postgres.integration.test.ts
📝 Walkthrough

Walkthrough

Adds Phase 7 console depth: the API gains filtered/paginated GET /jobs, workspace file inspection endpoints, and quota data in /dashboard; the console gains a filterable jobs page with pagination, a workspace file viewer, sparkline/bar dashboard cards, role-gated config editing, accessibility ARIA improvements, and a jobActions utility. A hermetic Playwright E2E harness is wired via a new stub runner.

Changes

Phase 7: Console depth, API filtering/workspace, dashboard metrics, and E2E harness

Layer / File(s) Summary
Dashboard metrics: costTrend, byModel, active
packages/habenae/src/dashboard.ts, packages/habenae/src/dashboard.test.ts
DashboardData gains costTrend, byModel, and active fields; buildDashboard aggregates 14-day trend buckets, per-model cost/job counts, and per-factio active concurrency from the jobs loop.
API utilities: job-query parsing and workspace decode
apps/api/src/app.ts
Adds JOB_STATES allowlist, clampInt, toSeq, base64Bytes, decodeFile, and MAX_FILE_BYTES to support the new query-validated and binary-safe workspace endpoints.
API endpoints: filtered /jobs, workspace, dashboard quota
apps/api/src/app.ts, apps/api/src/app.test.ts, docs/api.md
Rewrites GET /jobs with tenant-scoped filtering, search, pagination, and newest-first sort; adds GET /jobs/:id/workspace and GET /jobs/:id/workspace/file with tenant isolation and binary-safe decoding; conditionally merges quota data into GET /dashboard. Tests and API docs updated accordingly.
Stub runner and E2E server wiring
apps/api/src/stub-runner.ts, apps/api/src/index.ts, apps/api/package.json, apps/console/playwright.config.ts, .github/workflows/ci.yml, apps/console/package.json, apps/console/.gitignore, package.json, packages/habenae/src/postgres.integration.test.ts
Creates createStubRunner (single-flight Worker + StubProvider writing answer.txt); wires it into index.ts via AURIGA_STUB_RUNNER=1; adds Playwright config spawning stub API + Next dev server; adds e2e CI job, root and API dev scripts, and related package/gitignore entries; fixes async NOTIFY poll in postgres test.
Console API client types
apps/console/lib/api.ts
Extends Dashboard, Job; adds JobsQuery, JobsPage, WorkspaceEntry, WorkspaceManifest, WorkspaceFile; updates api.jobs to accept query params and return JobsPage; adds api.workspace.
jobActions utility
apps/console/lib/job-actions.ts, apps/console/lib/job-actions.test.ts
Adds JobActionTarget, JobActions, and jobActions() deriving all lifecycle UI flags from job state, approval, and spec; fully tested.
Pagination, sparkline, file-tree utilities
apps/console/lib/pager.ts, apps/console/lib/sparkline.ts, apps/console/lib/file-tree.ts, apps/console/lib/pager.test.ts, apps/console/lib/sparkline.test.ts, apps/console/lib/file-tree.test.ts
Three pure utility modules with full test coverage: pagerBounds, sparklinePoints, and buildFileTree/sortTree.
RunTimelinePanel, WorkspaceViewer, and workspace proxy route
apps/console/components/run-timeline-panel.tsx, apps/console/components/run-timeline.tsx, apps/console/components/workspace-viewer.tsx, apps/console/app/api/jobs/[id]/workspace/file/route.ts, apps/console/components/live-run.tsx
Adds RunTimelinePanel (Steps/Logs tabs with filter); extends RunTimeline with optional filter prop; adds WorkspaceViewer (tree sidebar + lazy file fetch); adds Next.js same-origin proxy route forwarding auth headers to the control plane.
Dashboard UI: sparkline, bar, and quota cards
apps/console/components/sparkline.tsx, apps/console/components/ui/bar.tsx, apps/console/components/ui/field.tsx, apps/console/app/page.tsx
Adds Sparkline SVG component and Bar progress component; updates dashboard page with Cost trend, Cost by model (top 6), and conditional Quota utilization cards.
Config RBAC gating and ARIA improvements
apps/console/components/config-form.tsx, apps/console/components/config-form.test.tsx, apps/console/app/config/page.tsx, apps/console/components/job-form.tsx, apps/console/vitest.setup.ts
ConfigForm gains canEdit prop (fieldset disable, conditional buttons, read-only text, ARIA tab roles); ConfigPage fetches actor and passes canEdit={actor.role==="admin"}; JobForm inputs gain ARIA labels; Vitest setup adds RTL cleanup.
Filterable/paginated jobs page and row actions
apps/console/app/jobs/page.tsx, apps/console/components/jobs-filter-bar.tsx, apps/console/components/jobs-pager.tsx, apps/console/components/job-row-actions.tsx
JobsPage accepts searchParams and calls api.jobs with parsed filters/pagination; adds JobsFilterBar (search, state, date range), JobsPager (prev/next with URL param sync), and JobRowActions (inline lifecycle buttons).
Job detail page: jobActions wiring and WorkspaceViewer
apps/console/app/jobs/[id]/page.tsx
Replaces ad-hoc boolean derivations with jobActions(); swaps RunTimeline for RunTimelinePanel; renders WorkspaceViewer when workspace files are present.
Playwright E2E tests and README
apps/console/e2e/run.spec.ts, README.md
Adds four E2E tests: full run-to-done with workspace check, cancel flow, config read-only for viewer role, and Axe accessibility scan on core pages. README updated to mark Phases 0–6 complete, Phase 7 scope, and new bun run dev local workflow.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(100, 180, 255, 0.5)
    Note over Browser,StubAPI: E2E test: job run to done
  end
  participant Browser
  participant NextDev as Next.js console
  participant NextProxy as /api/jobs/[id]/workspace/file
  participant StubAPI as API (AURIGA_STUB_RUNNER=1)
  participant StubRunner as createStubRunner

  Browser->>NextDev: login, createJob (Raw JSON tab)
  NextDev->>StubAPI: POST /jobs
  StubAPI-->>NextDev: { id }
  Browser->>NextDev: click Run
  NextDev->>StubAPI: POST /jobs/:id/run
  StubAPI->>StubRunner: run(jobId)
  StubRunner->>StubRunner: Worker + StubProvider → writes answer.txt
  StubRunner-->>StubAPI: done
  Browser->>NextDev: poll job state via SSE
  NextDev-->>Browser: state=done
  Browser->>NextDev: navigate workspace tab
  NextDev->>StubAPI: GET /jobs/:id/workspace
  StubAPI-->>NextDev: { files: ["answer.txt"] }
  Browser->>NextProxy: GET /api/jobs/:id/workspace/file?path=answer.txt
  NextProxy->>StubAPI: GET /jobs/:id/workspace/file?path=answer.txt (authHeaders)
  StubAPI-->>NextProxy: { encoding:"utf8", content:"ok\n", truncated:false }
  NextProxy-->>Browser: proxied JSON
  Browser-->>Browser: assert answer.txt visible
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • ainaive/auriga#6: Both PRs modify the Auriga control-plane HTTP API—especially apps/api/src/app.ts's job-listing/tenant-scoped /jobs behavior and the /dashboard endpoint wiring to buildDashboard—so the main PR's API changes are directly related.
  • ainaive/auriga#10: Both PRs modify the console job detail page (apps/console/app/jobs/[id]/page.tsx) to conditionally render job actions based on job state (retrieved PR adds ApproveButton for paused jobs; main PR refactors the action-button rendering to derive needsApproval/other flags via jobActions(job)).
  • ainaive/auriga#12: The main PR's console updates tweak the existing live-run UI layer by switching apps/console/components/live-run.tsx from rendering RunTimeline to the new RunTimelinePanel, directly building on the retrieved PR's SSE LiveRun implementation.

Poem

🐇 Hop hop, the workspace blooms,
Filter bars and sparkline grooms,
Stub runner wakes without the cloud,
Axe finds no faults — the rabbit's proud!
Phases tick, the timeline glows,
answer.txt confirms it: done! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the primary changes: implementing web-first phases for workspace viewing, jobs list filtering/pagination, observability dashboards, and E2E hardening. It directly reflects the changeset's scope and intent.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-phase-7-10

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

hutusi added 4 commits June 20, 2026 09:22
Extend buildDashboard (single extra pass over the jobs it already iterates) with
costTrend (bucketed by created_at day, last 14), byModel (grouped, NaN-guarded
cost), and active (in-flight count per factio). GET /dashboard joins the config
quotas when a config store is wired.

Console: a cost-trend sparkline, per-model cost bars, and quota-utilization bars
(amber ≥ 80%) — all CSS/inline-SVG, no chart dependency. Pure sparklinePoints +
a reusable Bar.

Bun tests cover byModel/costTrend/active + /dashboard quotas present/absent;
Vitest covers sparklinePoints.
Non-admins see the config form disabled (a wrapping fieldset) with the Save/Add/
Remove actions hidden and a read-only note; admins edit as before. The API
already enforces admin on PUT /config — this is defense-in-depth + UX. Also fix
the Vitest setup to unmount React trees between tests (cleanup), which a render
test surfaced. Vitest covers the disabled-vs-editable states.
10a — a network-free browser E2E: a stub runner (AURIGA_STUB_RUNNER=1) wires the
Worker with StubProvider + the in-memory bus so /run streams real state/trace/
progress/done to completion. Playwright boots the API + Next console and drives
login → create (raw-JSON spec) → run → watch the live timeline reach done +
workspace file, cancel a pending job, and config read-only as a viewer. New CI
e2e job (chromium), kept off the Bun + console gates.

10b — accessibility baseline: an @axe-core/playwright assertion (no serious/
critical) on dashboard/jobs/config/login; Field now wraps its control in the
label (associated without an explicit id), the form tabs are a proper tablist,
and the criterion controls get aria-labels.

All 4 E2E specs pass locally (incl. axe); root + console gates green.
@hutusi
hutusi marked this pull request as ready for review June 20, 2026 01:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
docs/api.md (1)

38-55: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add the new workspace routes to the Routes table.

GET /jobs/:id/workspace and GET /jobs/:id/workspace/file are implemented but not listed here, so API docs are currently incomplete for workspace inspection consumers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/api.md` around lines 38 - 55, Add two new rows to the Routes table in
the API documentation to include the missing workspace inspection endpoints. Add
rows for GET /jobs/:id/workspace and GET /jobs/:id/workspace/file with tenant
authentication level, appropriate descriptions indicating they are for workspace
inspection, and reasonable status codes (200, 401, 404) consistent with similar
job-related GET endpoints in the table.
apps/console/components/config-form.tsx (1)

79-85: ⚠️ Potential issue | 🟡 Minor

Implement complete WAI-ARIA tabs pattern or use native button semantics.

Lines 79 and 182 introduce role="tablist" and role="tab" with aria-selected, but the implementation lacks required ARIA structure and keyboard behavior:

  • Missing aria-controls on tabs linking to their panels
  • Missing role="tabpanel" on content sections with aria-labelledby back-references
  • Missing tabindex management (active tab should be 0, inactive -1)
  • No keyboard navigation (arrow keys, Home/End, Space/Enter)

This partial ARIA can confuse assistive technology. Either complete the tabs pattern per WAI-ARIA APG or drop the roles and rely on button semantics alone.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/components/config-form.tsx` around lines 79 - 85, The tabs
implementation at lines 79-85 with the div containing role="tablist" and the
ModeTab components is missing critical WAI-ARIA accessibility requirements. You
need to either complete the full tabs pattern by adding aria-controls attributes
on each ModeTab linking to corresponding panel IDs, adding role="tabpanel" with
aria-labelledby on the content sections (around line 182 and beyond),
implementing tabindex management where the active tab is 0 and inactive tabs are
-1, and adding keyboard event handlers for arrow keys (left/right), Home/End
keys, and Enter/Space; OR simplify by removing the role attributes entirely and
relying on native button semantics in the ModeTab components instead. Choose one
approach and implement it consistently throughout the component.
apps/console/components/job-form.tsx (1)

70-76: ⚠️ Potential issue | 🟡 Minor

ModeTab implementation is missing required ARIA tab pattern attributes and keyboard support.

The tabs have role="tab" and aria-selected, but lack the complete WAI-ARIA structure:

  • Content panels need role="tabpanel" and aria-labelledby (linked to the tab ID)
  • Each tab needs aria-controls pointing to its panel ID
  • Missing tabindex management: active tab should be tabindex="0", inactive tabs tabindex="-1"
  • No keyboard navigation (arrow keys, Home, End)

Either implement the full tabs pattern or use plain button semantics.

Also applies to: 237-250

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/components/job-form.tsx` around lines 70 - 76, The ModeTab
component and tab structure lack proper WAI-ARIA attributes required for
accessible tabs. Add aria-selected, aria-controls, and tabindex management to
each ModeTab instance (active tab tabindex="0", inactive tabindex="-1"), add
unique IDs to each tab and wrap the corresponding content panels in divs with
role="tabpanel" and aria-labelledby attributes linking back to the tab IDs.
Implement keyboard navigation in the ModeTab component to handle arrow keys
(left/right to switch tabs), Home (first tab), and End (last tab). Apply these
changes consistently to both tab implementations in the file at lines 70-76 and
237-250.
🧹 Nitpick comments (1)
apps/console/app/jobs/[id]/page.tsx (1)

25-26: ⚡ Quick win

Parallelize independent trace/workspace fetches to reduce server render latency.

For non-live jobs these two awaits are serialized, adding one extra round-trip to TTFB.

Suggested refactor
-  const trace = live ? null : await api.trace(id);
-  const ws = await api.workspace(id);
+  const [trace, ws] = await Promise.all([
+    live ? Promise.resolve(null) : api.trace(id),
+    api.workspace(id),
+  ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/app/jobs/`[id]/page.tsx around lines 25 - 26, The api.trace()
and api.workspace() calls are independent operations but are currently awaited
sequentially, causing unnecessary latency. Refactor these calls to execute in
parallel by starting both promises without immediate await. Create promises for
both api.trace(id) and api.workspace(id) calls, then use Promise.all() to await
both results simultaneously while preserving the conditional logic that sets
trace to null when live is true. This will reduce the number of round-trips to
the server.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/app.ts`:
- Around line 132-143: The created_after and created_before query parameters
(captured in the after and before variables) are being used directly in string
comparisons against j.created_at without validation or normalization. This
causes issues with date-only ISO inputs and silently accepts malformed values.
Before performing the filtering operations on lines 141-142, add validation
logic that parses both after and before as valid ISO datetime strings,
normalizes them to a consistent datetime format, and returns a 400 error
response if either value is invalid or malformed. Only proceed with the filter
comparisons after successful validation and normalization.

In `@apps/console/app/api/jobs/`[id]/workspace/file/route.ts:
- Around line 23-29: The upstream fetch call lacks an explicit timeout, allowing
it to hang indefinitely on half-open connections. Add an AbortSignal with a
10-second timeout to the fetch options in the upstream variable assignment. Then
update the catch block to differentiate between timeout errors (which should
return a 504 status) and other connection errors (which should return a 502
status), so you can check if the caught error is a timeout-related error and
respond accordingly.

In `@apps/console/app/page.tsx`:
- Around line 66-67: The warn prop assignment that divides totalActive by
dash.quotas.global can produce Infinity or NaN when dash.quotas.global equals
zero, leading to incorrect warning coloring. Add a guard condition to check that
dash.quotas.global is greater than zero before performing the division. Apply
this same fix to both occurrences mentioned (the one at lines 66-67 and the one
at lines 75-76) to ensure both quota warning ratios are protected against zero
divisors.

In `@apps/console/components/jobs-filter-bar.tsx`:
- Around line 25-28: The filter state variables (q, state, after, before) are
only initialized from URL params once during component mount, so they become
stale when the URL changes through router.push or browser history navigation.
Add a useEffect hook that depends on the params object and updates all four
state variables whenever the URL parameters change. This will ensure the filter
form always reflects the current URL state. Apply the same fix to the similar
state variables mentioned at lines 82-84.

In `@apps/console/components/workspace-viewer.tsx`:
- Around line 16-30: The open function has a race condition where a delayed
response from opening file A can overwrite the state after file B is already
selected. To fix this, after the fetch request completes in the open function,
add a check before calling setFile to verify that the path parameter still
matches the currently selected path. Only update the file state if the path
matches, otherwise the stale response will be ignored and the correct file will
remain displayed.
- Around line 35-41: Remove the incomplete ARIA tree semantics from the
workspace-viewer component. Delete the role="tree" and aria-label="workspace
files" attributes from the div container since this is a simple styled list and
not a fully interactive tree widget with keyboard navigation support.
Additionally, remove role="treeitem" and aria-selected attributes from the Tree
component implementation (where the file buttons are rendered around lines
100-101) to ensure consistent and honest semantic markup that won't mislead
assistive technology users.

In `@apps/console/e2e/run.spec.ts`:
- Around line 33-50: The tests use fixed job IDs "e2e_run" and "e2e_cancel"
passed to the createJob function, which causes collisions when tests are retried
in CI because previous attempts leave behind jobs with the same IDs. To fix
this, modify each call to createJob to generate unique identifiers by appending
a timestamp or random value to the fixed job ID strings, ensuring that each test
attempt creates a fresh job that won't collide with jobs from previous attempts.

In `@apps/console/lib/pager.ts`:
- Around line 12-20: The pagerBounds function does not validate the limit
parameter before using it in division operations on the page and pages fields.
If limit is zero or negative (from bad API payloads or misuse),
Math.floor(offset / limit) and Math.ceil(total / limit) will produce Infinity or
NaN values. Add a guard clause at the beginning of the pagerBounds function to
validate that limit is a positive number greater than zero, and handle the
invalid case appropriately (either throw an error or return a safe default
state).

---

Outside diff comments:
In `@apps/console/components/config-form.tsx`:
- Around line 79-85: The tabs implementation at lines 79-85 with the div
containing role="tablist" and the ModeTab components is missing critical
WAI-ARIA accessibility requirements. You need to either complete the full tabs
pattern by adding aria-controls attributes on each ModeTab linking to
corresponding panel IDs, adding role="tabpanel" with aria-labelledby on the
content sections (around line 182 and beyond), implementing tabindex management
where the active tab is 0 and inactive tabs are -1, and adding keyboard event
handlers for arrow keys (left/right), Home/End keys, and Enter/Space; OR
simplify by removing the role attributes entirely and relying on native button
semantics in the ModeTab components instead. Choose one approach and implement
it consistently throughout the component.

In `@apps/console/components/job-form.tsx`:
- Around line 70-76: The ModeTab component and tab structure lack proper
WAI-ARIA attributes required for accessible tabs. Add aria-selected,
aria-controls, and tabindex management to each ModeTab instance (active tab
tabindex="0", inactive tabindex="-1"), add unique IDs to each tab and wrap the
corresponding content panels in divs with role="tabpanel" and aria-labelledby
attributes linking back to the tab IDs. Implement keyboard navigation in the
ModeTab component to handle arrow keys (left/right to switch tabs), Home (first
tab), and End (last tab). Apply these changes consistently to both tab
implementations in the file at lines 70-76 and 237-250.

In `@docs/api.md`:
- Around line 38-55: Add two new rows to the Routes table in the API
documentation to include the missing workspace inspection endpoints. Add rows
for GET /jobs/:id/workspace and GET /jobs/:id/workspace/file with tenant
authentication level, appropriate descriptions indicating they are for workspace
inspection, and reasonable status codes (200, 401, 404) consistent with similar
job-related GET endpoints in the table.

---

Nitpick comments:
In `@apps/console/app/jobs/`[id]/page.tsx:
- Around line 25-26: The api.trace() and api.workspace() calls are independent
operations but are currently awaited sequentially, causing unnecessary latency.
Refactor these calls to execute in parallel by starting both promises without
immediate await. Create promises for both api.trace(id) and api.workspace(id)
calls, then use Promise.all() to await both results simultaneously while
preserving the conditional logic that sets trace to null when live is true. This
will reduce the number of round-trips to the server.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 618c220c-a309-43e4-86c4-8ec911a2f907

📥 Commits

Reviewing files that changed from the base of the PR and between cce9ead and caf17b6.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (41)
  • .github/workflows/ci.yml
  • README.md
  • apps/api/src/app.test.ts
  • apps/api/src/app.ts
  • apps/api/src/index.ts
  • apps/api/src/stub-runner.ts
  • apps/console/.gitignore
  • apps/console/app/api/jobs/[id]/workspace/file/route.ts
  • apps/console/app/config/page.tsx
  • apps/console/app/jobs/[id]/page.tsx
  • apps/console/app/jobs/page.tsx
  • apps/console/app/page.tsx
  • apps/console/components/config-form.test.tsx
  • apps/console/components/config-form.tsx
  • apps/console/components/job-form.tsx
  • apps/console/components/job-row-actions.tsx
  • apps/console/components/jobs-filter-bar.tsx
  • apps/console/components/jobs-pager.tsx
  • apps/console/components/live-run.tsx
  • apps/console/components/run-timeline-panel.tsx
  • apps/console/components/run-timeline.tsx
  • apps/console/components/sparkline.tsx
  • apps/console/components/ui/bar.tsx
  • apps/console/components/ui/field.tsx
  • apps/console/components/workspace-viewer.tsx
  • apps/console/e2e/run.spec.ts
  • apps/console/lib/api.ts
  • apps/console/lib/file-tree.test.ts
  • apps/console/lib/file-tree.ts
  • apps/console/lib/job-actions.test.ts
  • apps/console/lib/job-actions.ts
  • apps/console/lib/pager.test.ts
  • apps/console/lib/pager.ts
  • apps/console/lib/sparkline.test.ts
  • apps/console/lib/sparkline.ts
  • apps/console/package.json
  • apps/console/playwright.config.ts
  • apps/console/vitest.setup.ts
  • docs/api.md
  • packages/habenae/src/dashboard.test.ts
  • packages/habenae/src/dashboard.ts

Comment thread apps/api/src/app.ts Outdated
Comment thread apps/console/app/api/jobs/[id]/workspace/file/route.ts
Comment thread apps/console/app/page.tsx Outdated
Comment thread apps/console/components/jobs-filter-bar.tsx
Comment thread apps/console/components/workspace-viewer.tsx
Comment thread apps/console/components/workspace-viewer.tsx
Comment thread apps/console/e2e/run.spec.ts
Comment thread apps/console/lib/pager.ts
hutusi added 3 commits June 20, 2026 12:21
- api: parse created_after/before with Date.parse (correct date-only filtering;
  400 on malformed) instead of raw string comparison.
- console proxy: AbortSignal.timeout(10s) on the workspace-file fetch.
- dashboard: guard quota-utilization warning against zero quotas.
- jobs-filter-bar: re-sync inputs when the URL filters change (Clear/back/links).
- workspace-viewer: ignore a stale file response (superseded click); drop the
  incomplete tree ARIA (buttons + aria-current — Tab-navigable, not misleading).
- e2e: unique job ids per call so a Playwright retry can't collide.
- pager: guard a non-positive limit against Infinity/NaN.

Root + console gates green; all 4 E2E specs pass (incl. axe).
…test

The cross-connection PostgresEventBus assertion used a fixed 400ms wait before
checking received events — flaky under CI load. Poll until both events arrive
(up to 5s) instead of racing a fixed timeout. Gated on DATABASE_URL (CI only).
Run the whole web stack with one command (Bun --filter across the two apps).
The API dev script runs from the repo root (so ./fixtures resolves) and defaults
to the stub runner, so `bun run dev` gives a working live-run demo with no API
key or Docker. Documented in the README; real runs start the API with a key.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/habenae/src/postgres.integration.test.ts`:
- Around line 139-141: Replace the wall-clock based Date.now() calls with a
monotonic timer in the polling loop. Change line 139 where the deadline is
calculated and line 140 where the deadline is checked in the while condition to
use performance.now() or process.hrtime.bigint() instead of Date.now(), ensuring
the elapsed time check uses monotonic time that cannot jump backwards during CI
execution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bc698351-8cc0-4cd7-b361-98a9145ec339

📥 Commits

Reviewing files that changed from the base of the PR and between e530c3e and db1fcb8.

📒 Files selected for processing (4)
  • README.md
  • apps/api/package.json
  • package.json
  • packages/habenae/src/postgres.integration.test.ts
✅ Files skipped from review due to trivial changes (2)
  • package.json
  • README.md

Comment thread packages/habenae/src/postgres.integration.test.ts Outdated
performance.now() instead of Date.now() so the NOTIFY poll timeout can't be
skewed by a wall-clock jump in CI (CodeRabbit nit).
@hutusi
hutusi merged commit 13d2797 into main Jun 20, 2026
5 checks passed
@hutusi
hutusi deleted the feat/web-phase-7-10 branch June 20, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant