Skip to content

feat(web): wire dashboard loaders, mutations, and error toasts (backlog #4)#46

Merged
isuttell merged 5 commits into
mainfrom
agents/web-loader-wiring
May 24, 2026
Merged

feat(web): wire dashboard loaders, mutations, and error toasts (backlog #4)#46
isuttell merged 5 commits into
mainfrom
agents/web-loader-wiring

Conversation

@isuttell

@isuttell isuttell commented May 24, 2026

Copy link
Copy Markdown
Contributor

What

Wires the apps/web dashboard to live data, the backlog #4 "web follow-up wiring" item in docs/ops/web-app-todo.md. All backing /v1/web/* endpoints already exist; this connects the UI to them.

  • Loaders: dashboard (/v1/web/workspace + recent artifacts + audit via Promise.all), artifacts index/detail, audit, and settings now fetch real data. Genuine zero-row states still render EmptyState. access-links and the admin lockdown list stay as EmptyState (Phase 4 / deferred endpoints, out of scope).
  • Mutations (src/server/web-mutations.ts): createKey, revokeKey, saveSettings via createServerFnapiFetch with the WorkOS access token and a per-request Idempotency-Key. Inputs are parsed against the canonical @agent-paste/contracts schemas before the upstream call. One-time secrets (first-run default key + create-key response) live in component state only, never persisted or logged.
  • First-run key card: rendered when GET /v1/web/workspace returns default_key_first_run = true.
  • Error toasts: ToastProvider/ToastList surface api error envelopes (code + message + a /audit?request_id=… link).

Tests

  • 30 component/loader tests across 10 files (jsdom + mocked server fns / api responses).
  • pnpm verify green (70/70 Turbo tasks).
  • Local CodeRabbit reviewed across 3 rounds; all actionable findings fixed (timer cleanup, client+server range validation, audit aria-current, status-tone consistency, dead-fallback removal). Two finding classes declined with rationale: userEvent-over-fireEvent (not a repo dependency; matches existing test convention) and "derive idempotency key server-side" (a fresh key per click is correct; the API owns idempotency).

Not verified here

Live-preview rendering against a real WorkOS login is not verified: it depends on Isaac's WorkOS project + Cloudflare Access click-ops (backlog #1). Verification here is static (typecheck/lint/build) plus jsdom component tests. smoke:web + CI deploy remain separate, blocked backlog items.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Data-driven dashboard: usage policy, recent artifacts, recent activity, and "View all artifacts".
    • First-run API key card with one-time secret reveal and one-time new-key secret display.
    • API key management: create keys (one-time secret) and revoke keys with immediate feedback.
    • Toast notifications for success/errors with links to audit entries.
    • Workspace settings form for editing name and auto-deletion.
    • Artifact pages show status badges, metadata, and pinned/locked indicators.
  • Documentation

    • Updated checklist reflecting completed dashboard and notification work.

Review Change Stack

isuttell and others added 4 commits May 24, 2026 14:01
…oasts

Replace the EmptyState/no-loader fallbacks across the authed dashboard with
real loaders against the existing /v1/web/* endpoints, wire the previously
dead mutations, and surface api error envelopes as toasts.

- dashboard: loads GET /v1/web/workspace + recent artifacts + recent audit in
  parallel; renders a usage-policy card, recent tables, and a first-run key
  card gated on default_key_first_run (secret held in component state only).
- artifacts: index rows link to detail; detail surfaces entrypoint, file
  count, size, and pinned/lockdown badges.
- keys: KeyCreateForm (POST /v1/web/keys) reveals the one-time secret in state;
  KeysTable Revoke (POST /v1/web/keys/{id}/revoke) refreshes via router
  invalidate. Both go through createServerFn -> apiFetch with a generated
  Idempotency-Key.
- settings: SettingsForm saves name + auto-deletion days (PATCH
  /v1/web/settings).
- audit: reads ?request_id= and highlights the matching row.
- toasts: ToastProvider/useToast mounted in _authed plus an errorToast helper;
  failures show code + message + a link to /audit?request_id=<id>.
- Access Links and the operator lockdown list stay EmptyState (endpoints
  deferred / Phase 4).

Adds Vitest coverage for the toast mechanism, first-run card, key create/revoke,
settings save, and dashboard cards. pnpm verify green (70 tasks).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ToastProvider: clear pending auto-dismiss timers on unmount
- SettingsForm: reject auto-deletion days outside 1..90 client-side
- audit: mark the request_id-matched row with aria-current
- test: cover the out-of-range auto-deletion guard

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address CodeRabbit round 2:
- web-mutations: validate createKey/revokeKey/saveSettings inputs against the
  canonical contract schemas (CreateApiKeyRequest, ApiKeyId,
  UpdateWebSettingsRequest) so malformed payloads never reach the API
- artifact-status: map Expired->warning and Deleted->destructive badge tones,
  shared by the dashboard and artifacts index
- FirstRunKeyCard: clarify the missing-secret copy
- keys: memoize the refresh callback

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address CodeRabbit round 3:
- artifacts detail route now uses the shared artifactStatusTone helper
- remove the unreachable neutral fallback (the tone map is exhaustive over
  the WebArtifactStatus enum)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@isuttell isuttell temporarily deployed to pr-preview-46 May 24, 2026 21:33 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8c88dece-1687-42ba-a724-e014b45407d1

📥 Commits

Reviewing files that changed from the base of the PR and between b24490d and 4e0b1cf.

📒 Files selected for processing (4)
  • apps/web/src/components/settings/SettingsForm.tsx
  • apps/web/src/routes/_authed.artifacts.$artifactId.tsx
  • apps/web/src/routes/_authed.artifacts.index.tsx
  • apps/web/src/server/web-mutations.ts

Walkthrough

Adds a data-driven dashboard loader plus dashboard cards (usage, recent artifacts, recent audit, first-run key). Introduces toast context/provider/UI and integrates it into the authed layout. Adds validated server mutations for key creation/revocation and settings update. Adds KeyCreateForm, KeysTable, NewKeySecretCard, SettingsForm, artifact-status utility, audit request_id highlighting, and tests for components and server flows.

Sequence Diagram(s)

sequenceDiagram
  participant Page as DashboardPage
  participant Loader as loadDashboardFn
  participant API as Upstream API
  Page->>Loader: request workspace/artifacts/audit
  Loader->>API: parallel fetch workspace, artifacts, audit
  API-->>Loader: return data/errors
  Loader-->>Page: { workspace, artifacts, audit }
  Page->>Page: render UsagePolicy / FirstRunKeyCard / RecentArtifacts / RecentAudit
Loading
sequenceDiagram
  participant User
  participant KeyForm as KeyCreateForm
  participant Server as createKeyFn
  participant Upstream as /v1/web/keys
  User->>KeyForm: submit name
  KeyForm->>Server: createKeyFn(payload)
  Server->>Upstream: POST /v1/web/keys (idempotency-key)
  Upstream-->>Server: response
  Server-->>KeyForm: MutationResult (data or error)
  KeyForm->>User: show secret or error toast
Loading

Possibly related PRs

  • zaks-io/agent-paste#44: Related backend changes for PATCH /v1/web/settings used by the new SettingsForm/saveSettingsFn.

Poem

🐰 I polished the dashboard bright and quick,
Keys tucked safely in a single click,
Toasts sing errors, soft and clear,
Forms validate and quietly cheer,
I hop away — the UI feels slick!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 title clearly and specifically describes the main change: wiring dashboard loaders, mutations, and error toasts with reference to the backlog item, which directly aligns with all the file changes in the PR.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agents/web-loader-wiring

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/routes/_authed.artifacts.index.tsx (1)

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

Update column header to match content.

The column header reads "Actions" but the corresponding cells (lines 83–85) only display a "Pinned" badge with no interactive actions. The AI summary confirms that "the previous trailing ellipsis/actions cell was removed."

🔧 Proposed fix
-              <TH className="text-right">Actions</TH>
+              <TH className="text-right">Pinned</TH>

Or remove the header entirely if the column is purely informational:

-              <TH className="text-right">Actions</TH>
+              <TH className="text-right"></TH>
🤖 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/web/src/routes/_authed.artifacts.index.tsx` at line 55, The table header
currently reads "Actions" (the <TH className="text-right">Actions</TH>) but the
corresponding cells only render a non-interactive "Pinned" badge (the code that
outputs the "Pinned" badge in the row cells), so update the header to match the
content (e.g., change "Actions" to "Status" or "Pinned") or remove the <TH>
entirely if you prefer no header for that informational column; make the change
next to the existing <TH className="text-right"> and the code that renders the
"Pinned" badge so they stay semantically consistent.
🤖 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/web/src/components/settings/SettingsForm.tsx`:
- Around line 20-35: After a successful save in the Save handler (where
saveSettingsFn is called and parsedDays is computed), normalize the local form
state to match the persisted values: call setName(name.trim()) and
setDays(String(parsedDays)) immediately after router.invalidate() (or before
resolving) so the inputs reflect the trimmed workspace_name and numeric
auto_deletion_days; ensure this runs only when result.error is falsy and
preserves the existing setPending(false)/push(...) flow.

In `@apps/web/src/routes/_authed.artifacts.index.tsx`:
- Line 67: The link currently renders row.title directly and can be blank when
title is null/undefined/empty; update the rendering logic in the component that
outputs {row.title} to use a fallback (e.g., row.title || 'Untitled' or prefer
row.fileName || 'Untitled artifact') so a sensible string is shown when title is
missing; locate the place that renders row.title in the artifacts index route
and replace it with a fallback expression or helper that normalizes empty
strings as well as null/undefined.

In `@apps/web/src/server/web-mutations.ts`:
- Around line 89-91: The handler currently accesses data.apiKeyId directly which
will throw if data is null/not an object; change the handler in web-mutations.ts
(the .handler(({ data }) => { ... }) block) to validate that data is an object
before reading apiKeyId (e.g., check data != null && typeof data === 'object' or
use safe access like data?.apiKeyId) and if invalid return the same
Promise.resolve({ data: null, error: <validation_error> }) contract; then call
parseInput(ApiKeyId, apiKeyIdValue) only after the safe check so parseInput is
never passed an access that can throw.

---

Outside diff comments:
In `@apps/web/src/routes/_authed.artifacts.index.tsx`:
- Line 55: The table header currently reads "Actions" (the <TH
className="text-right">Actions</TH>) but the corresponding cells only render a
non-interactive "Pinned" badge (the code that outputs the "Pinned" badge in the
row cells), so update the header to match the content (e.g., change "Actions" to
"Status" or "Pinned") or remove the <TH> entirely if you prefer no header for
that informational column; make the change next to the existing <TH
className="text-right"> and the code that renders the "Pinned" badge so they
stay semantically consistent.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8f564cb1-8011-48ae-8cd1-3e7e2e0f3d33

📥 Commits

Reviewing files that changed from the base of the PR and between dac72d1 and b24490d.

📒 Files selected for processing (29)
  • apps/web/src/components/dashboard/FirstRunKeyCard.tsx
  • apps/web/src/components/dashboard/RecentArtifacts.tsx
  • apps/web/src/components/dashboard/RecentAudit.tsx
  • apps/web/src/components/dashboard/UsagePolicyCard.tsx
  • apps/web/src/components/keys/KeyCreateForm.tsx
  • apps/web/src/components/keys/KeysTable.tsx
  • apps/web/src/components/keys/NewKeySecretCard.tsx
  • apps/web/src/components/settings/SettingsForm.tsx
  • apps/web/src/components/ui/ToastList.tsx
  • apps/web/src/components/ui/ToastProvider.tsx
  • apps/web/src/components/ui/toast-context.ts
  • apps/web/src/lib/artifact-status.ts
  • apps/web/src/routes/_authed.artifacts.$artifactId.tsx
  • apps/web/src/routes/_authed.artifacts.index.tsx
  • apps/web/src/routes/_authed.audit.tsx
  • apps/web/src/routes/_authed.dashboard.tsx
  • apps/web/src/routes/_authed.keys.tsx
  • apps/web/src/routes/_authed.settings.tsx
  • apps/web/src/routes/_authed.tsx
  • apps/web/src/server/web-mutations.ts
  • apps/web/test/FirstRunKeyCard.test.tsx
  • apps/web/test/KeyCreateForm.test.tsx
  • apps/web/test/KeysTable.test.tsx
  • apps/web/test/RecentAudit.test.tsx
  • apps/web/test/SettingsForm.test.tsx
  • apps/web/test/UsagePolicyCard.test.tsx
  • apps/web/test/artifact-status.test.ts
  • apps/web/test/toast.test.tsx
  • docs/ops/web-app-todo.md

Comment thread apps/web/src/components/settings/SettingsForm.tsx
Comment thread apps/web/src/routes/_authed.artifacts.index.tsx Outdated
Comment thread apps/web/src/server/web-mutations.ts
- SettingsForm: sync local name/days state to the persisted server response
  after save so the form stops showing stale (untrimmed/unclamped) input.
- Artifacts index + detail: render "Untitled" when an artifact title is empty.
- revokeKeyFn: read data?.apiKeyId defensively so a malformed server-fn
  payload yields a 400 validation envelope instead of a TypeError.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@isuttell isuttell temporarily deployed to pr-preview-46 May 24, 2026 21:43 — with GitHub Actions Inactive
@isuttell isuttell merged commit ad85175 into main May 24, 2026
4 checks passed
@isuttell isuttell deleted the agents/web-loader-wiring branch May 24, 2026 21:46
@github-actions

Copy link
Copy Markdown

agent-paste PR preview resources were cleaned up. The pr-preview-${context.issue.number} environment is left in place; remove it from the GitHub UI if desired.

isuttell added a commit that referenced this pull request May 24, 2026
Snapshot now points at ad85175 (#46); record the operator lockdown (#45) and
web loader-wiring (#46) merges in Recently Completed; strike backlog #4 as done;
update the Phase 3 (~55%), web.md, ADR 0055 rows. Remaining Phase 3 code work is
CLI login (#5) and smoke:web (#6), both gated on the WorkOS/Access click-ops in
backlog item #1.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai coderabbitai Bot mentioned this pull request May 25, 2026
4 tasks
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