feat: web-first next phases (workspace viewer, jobs at scale, observability, hardening) - #13
Conversation
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.
|
Warning Review limit reached
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds Phase 7 console depth: the API gains filtered/paginated ChangesPhase 7: Console depth, API filtering/workspace, dashboard metrics, and E2E harness
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
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.
There was a problem hiding this comment.
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 winAdd the new workspace routes to the Routes table.
GET /jobs/:id/workspaceandGET /jobs/:id/workspace/fileare 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 | 🟡 MinorImplement complete WAI-ARIA tabs pattern or use native button semantics.
Lines 79 and 182 introduce
role="tablist"androle="tab"witharia-selected, but the implementation lacks required ARIA structure and keyboard behavior:
- Missing
aria-controlson tabs linking to their panels- Missing
role="tabpanel"on content sections witharia-labelledbyback-references- Missing
tabindexmanagement (active tab should be0, 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 | 🟡 MinorModeTab implementation is missing required ARIA tab pattern attributes and keyboard support.
The tabs have
role="tab"andaria-selected, but lack the complete WAI-ARIA structure:
- Content panels need
role="tabpanel"andaria-labelledby(linked to the tab ID)- Each tab needs
aria-controlspointing to its panel ID- Missing tabindex management: active tab should be
tabindex="0", inactive tabstabindex="-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 winParallelize 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
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (41)
.github/workflows/ci.ymlREADME.mdapps/api/src/app.test.tsapps/api/src/app.tsapps/api/src/index.tsapps/api/src/stub-runner.tsapps/console/.gitignoreapps/console/app/api/jobs/[id]/workspace/file/route.tsapps/console/app/config/page.tsxapps/console/app/jobs/[id]/page.tsxapps/console/app/jobs/page.tsxapps/console/app/page.tsxapps/console/components/config-form.test.tsxapps/console/components/config-form.tsxapps/console/components/job-form.tsxapps/console/components/job-row-actions.tsxapps/console/components/jobs-filter-bar.tsxapps/console/components/jobs-pager.tsxapps/console/components/live-run.tsxapps/console/components/run-timeline-panel.tsxapps/console/components/run-timeline.tsxapps/console/components/sparkline.tsxapps/console/components/ui/bar.tsxapps/console/components/ui/field.tsxapps/console/components/workspace-viewer.tsxapps/console/e2e/run.spec.tsapps/console/lib/api.tsapps/console/lib/file-tree.test.tsapps/console/lib/file-tree.tsapps/console/lib/job-actions.test.tsapps/console/lib/job-actions.tsapps/console/lib/pager.test.tsapps/console/lib/pager.tsapps/console/lib/sparkline.test.tsapps/console/lib/sparkline.tsapps/console/package.jsonapps/console/playwright.config.tsapps/console/vitest.setup.tsdocs/api.mdpackages/habenae/src/dashboard.test.tspackages/habenae/src/dashboard.ts
- 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
README.mdapps/api/package.jsonpackage.jsonpackages/habenae/src/postgres.integration.test.ts
✅ Files skipped from review due to trivial changes (2)
- package.json
- README.md
performance.now() instead of Date.now() so the NOTIFY poll timeout can't be skewed by a wall-clock jump in CI (CodeRabbit nit).
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
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 (purebuildFileTree) + lazy viewer via a same-origin proxy route, and a Steps / Logs timeline toggle (Logs = tool output).GET /jobsgainsstate/q/created_after/created_before/limit/offset(filtered overlistByFactio; 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, testedlib/job-actions.ts.Coming on this branch
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+ consoletypecheck/test/buildall pass. Phase 10 adds a CIe2ejob (kept off the Bun gate).🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
e2eexecutionDocumentation
Tests