{ "source": "77fd0890ddfeaf2bdd6d78183a2d989d9a4e8d4c", "outcome": "timeout; stale first spec input preserved as failure", "initialImplementation": "# Plan: User Dashboard Page\n\n## Context\nWe're shipping a new user dashboard at `/dashboard` showing recent activity,\nnotifications panel, and quick-action buttons. Users land here after login.\n\n## UI Scope\n- New React page component `UserDashboard.tsx` at `src/pages/`\n- Three new sub-components: `ActivityFeed`, `NotificationsPanel`, `QuickActions`\n- Tailwind CSS for layout, mobile-first responsive (breakpoints: sm/md/lg)\n- Empty state, loading skeleton, error state for each panel\n- Hover states + focus-visible outlines on every interactive element\n- Modal dialog for \"Mark all as read\" on notifications panel\n- Toast notification system for action feedback\n\n## Backend\n- New REST endpoint `GET /api/dashboard` returns `{ activity, notifications, quickActions }`\n- Backed by existing PostgreSQL tables; no schema changes\n\n## Out of scope\n- Dark mode (separate plan)\n- Personalization / customization (separate plan)\n\n## Existing product and application contracts\n\nThis is the existing single-role member workspace, not a new product or a new\nonboarding flow. Members currently visit three separate pages after login to\nresume work, check alerts, and inspect recent changes. In the team's last task\nwalkthrough, finding the next item took a median 75 seconds. The dashboard's\nsuccess measure is login-to-first-completed-task time, targeting 45 seconds,\nwith completed-task rate and permission-error rate as guardrails. Existing\nanalytics records login, action start, action completion, and permission errors;\nthe new page still needs its own exposure and interaction instrumentation.\n\nActivity is the immutable audit history of workspace changes. Notifications are\nmember-specific alerts with persistent read state; acknowledging an alert does\nnot alter audit history. The existing action registry supplies three actions\n(create an item, resume assigned work, invite a member), with stable IDs, labels,\nroute targets, and server-side eligibility predicates. These are links into\nexisting workflows; action ranking and a new configuration service do not exist.\n\nThe application already uses cookie sessions and workspace membership middleware.\nIts request context supplies the authenticated member and workspace IDs. Existing\nrepository methods apply both IDs where appropriate; callers do not accept a\nworkspace ID from query parameters. Mutations already require CSRF tokens. The\nnew dashboard endpoint must compose these methods and follow the same boundaries;\nits handler, authorization integration, and failure paths have not been written.\n\nExisting list methods return the latest 20 records plus a cursor and have indexed\nworkspace/member and created-at access paths. The existing full activity and\nnotification pages own older-page navigation. The member-scoped bulk-read API is\nidempotent and marks only notifications at or before the supplied snapshot time,\nso later arrivals remain unread. Existing HTTP clients expose typed unauthenticated,\nforbidden, validation, retryable-service, and network errors. Each dashboard panel\nstill needs to map these results to its loading, empty, error, retry, and success\nstates; the aggregate endpoint's response composition and partial-failure behavior\nremain new implementation work. No schema migration or new mutation API is needed.\n\nThe app already has Tailwind spacing/color/type tokens, a responsive page shell,\nbuttons, links, and a dialog primitive with focus trapping, Escape dismissal, and\nfocus return. These primitives do not implement any dashboard panel, confirmation\nflow, or toast system. The new modal and toast feedback must also work with keyboard\nand screen readers; existing accessibility policy requires named controls, a live\nregion for nonblocking feedback, sufficient contrast, and reduced-motion support.\nThe dashboard still needs its own layout, content hierarchy, mobile behavior, and\nstate-specific copy at sm/md/lg breakpoints.\n\nVitest, React Testing Library, and Playwright already run in CI. Existing fixtures\ncover authenticated members, another workspace, empty lists, and service failures;\nthere are no dashboard-specific tests yet. Existing staging feature flags and\nrequest/error metrics support a member-cohort rollout and rollback to the current\nlanding page. The dashboard's rollout criteria, endpoint performance checks,\ninteraction tests, and accessibility verification must be specified and added.\n\nAll dashboard screen, panel, aggregate-endpoint, modal, and toast work listed above\nis new. The existing contracts describe dependencies to reuse, not completed work\nor prior approval of an implementation approach.\n", "initialSha256": "2fdf0ece590925869fe25ae941301894f8f4505da6674302df25d5c4546fddbc", "acceptedBlock": "\n- Endpoint contract: `GET /api/dashboard` runs the existing activity list, notifications list, and action-registry eligibility calls under the request context's member and workspace IDs (never from query params), composes them with `Promise.allSettled`, and returns HTTP 200 with per-panel envelopes `{ status: 'ok', items, cursor? }` or `{ status: 'error', code }` plus a server-generated `fetchedAt` ISO timestamp. Unauthenticated → 401 and forbidden (not a workspace member) → 403 fail the whole request. Each repository call has a per-panel timeout (proposed 2s) that yields `status: 'error', code: 'timeout'` instead of failing the request. `code` values map to the existing typed client errors (`retryable-service`, `validation`, `timeout`, `unknown`).\n- Panel state machine: every panel (`ActivityFeed`, `NotificationsPanel`, `QuickActions`) renders exactly one of loading skeleton, empty, error (with a Retry button that refetches the whole endpoint), or success, driven by its envelope. Whole-request 401 redirects to login; 403 and network errors render a page-level error with Retry. Skeletons match final layout height at sm/md/lg to avoid layout shift.\n- Quick actions: the endpoint returns only actions whose server-side eligibility predicate passes, with stable ID, label, and route target from the existing registry; the panel renders them as links to existing workflows; zero eligible actions renders the panel empty state.\n- Activity and notifications panels show the first page (latest 20) only and each includes a \"View all\" link to the existing full activity or notifications page; no pagination on the dashboard.\n- Notifications panel header shows the unread count from the returned items; the \"Mark all as read\" control renders only when unread count is greater than zero.\n- Mark all as read: opens the existing dialog primitive (focus trap, Escape, focus return) with confirm and cancel; confirm calls the existing member-scoped bulk-read API with the CSRF token and `snapshotTime = fetchedAt` from the dashboard payload so later arrivals stay unread; while in flight the confirm button is disabled and shows progress; on success the dialog closes, unread items in the panel render as read, unread count goes to zero, and a success toast fires; on failure the dialog stays open with an inline error and the confirm button re-enabled. No optimistic update.\n- Toast system: an in-house `ToastProvider` + `useToast()` (no new dependency) renders one `role=\"status\"` `aria-live=\"polite\"` region; toasts auto-dismiss after 5s, pause on hover/focus, have a named dismiss button, and disable enter/exit animation when `prefers-reduced-motion: reduce`. Toast copy is announced once and is not the only place a failure is shown.\n- Timestamps in both panels render relative (\"4 min ago\") inside `