# Review package Task 5 v2 ## Commits 7086b8b fix: style active inspector view 092930e style: apply workbench visual system ## Stat .superpowers/sdd/task-5-report.md | 43 ++++ .../features/workbench/ProjectWorkbench.test.ts | 8 + apps/web/src/index.css | 245 ++++++++++++--------- 3 files changed, 187 insertions(+), 109 deletions(-) ## Diff diff --git a/.superpowers/sdd/task-5-report.md b/.superpowers/sdd/task-5-report.md new file mode 100644 index 0000000..d4fc752 --- /dev/null +++ b/.superpowers/sdd/task-5-report.md @@ -0,0 +1,43 @@ +# Task 5 Report: shadcn/ui-Inspired Visual System In CSS + +## Status + +Implemented and committed the Task 5 workbench visual system. + +## What Changed + +- Replaced `apps/web/src/index.css` with the specified neutral visual tokens, workbench desktop grid, mobile responsive breakpoint, shared component classes, and focus-visible styles. +- Added the active-channel `aria-pressed` assertion to `ProjectWorkbench.test.ts`. + +## Tests + +From `apps/web`: + +- `npm test -- --run src/features/workbench/ProjectWorkbench.test.ts` passed: 1 test file, 4 tests. +- `npm test -- --run` passed: 6 test files, 13 tests. +- `npm run build` passed: Vite production build completed successfully. + +## TDD Evidence + +The requested active-channel assertion was added before CSS changes and run immediately. It passed because `ProjectChannelSidebar` already exposed `aria-pressed` for the selected channel; this task preserves that existing semantic contract while styling the component. + +## Files Changed + +- `apps/web/src/index.css` +- `apps/web/src/features/workbench/ProjectWorkbench.test.ts` + +## Self-Review + +- CSS values and selectors follow the Task 5 brief verbatim. +- The layout uses the required 76px/280px/flexible/320px desktop columns and collapses to one column at 920px. +- Keyboard focus is visibly indicated for buttons and inputs. +- No React or shadcn/ui dependencies were added. +- The commit stages only the two task-owned implementation files; pre-existing untracked `.superpowers/sdd` artifacts remain untouched. + +## Concerns + +The original selector mismatch between `aria-selected` and the inspector's existing `aria-pressed` semantics was corrected in the review fix below. + +## Review Fix + +Updated the inspector active-state selector in `apps/web/src/index.css` from `[aria-selected='true']` to `[aria-pressed='true']` so it matches `ObjectInspector`'s existing accessible state without changing component semantics. diff --git a/apps/web/src/features/workbench/ProjectWorkbench.test.ts b/apps/web/src/features/workbench/ProjectWorkbench.test.ts index 61e15b1..f2b6e72 100644 --- a/apps/web/src/features/workbench/ProjectWorkbench.test.ts +++ b/apps/web/src/features/workbench/ProjectWorkbench.test.ts @@ -17,20 +17,28 @@ describe('ProjectWorkbench', () => { it('refreshes channels when switching projects', async () => { render(ProjectWorkbench, { props: { currentUser: { account: 'david@example.com' } } }); await fireEvent.click(screen.getByRole('button', { name: 'Project A2' })); expect(screen.getByRole('button', { name: 'Project A2' })).toHaveAttribute('aria-pressed', 'true'); expect(screen.getByText('Ops Dashboard')).toBeInTheDocument(); }); + it('marks active channel with aria-pressed', async () => { + render(ProjectWorkbench, { props: { currentUser: { account: 'david@example.com' } } }); + + await fireEvent.click(screen.getByRole('button', { name: 'Work Plan 8' })); + + expect(screen.getByRole('button', { name: 'Work Plan 8' })).toHaveAttribute('aria-pressed', 'true'); + }); + it('shows inspected task details in the inspector', async () => { render(ProjectWorkbench, { props: { currentUser: { account: 'david@example.com' } } }); const inspector = screen.getByLabelText('Object inspector'); expect(within(inspector).getByRole('button', { name: 'Discussion' })).toHaveAttribute('aria-pressed', 'true'); await fireEvent.click(screen.getByRole('button', { name: 'Work Plan 8' })); await fireEvent.click(screen.getByRole('button', { name: 'Inspect Confirm homepage information architecture' })); expect(within(inspector).getByRole('heading', { name: 'Confirm homepage information architecture' })).toBeInTheDocument(); diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 7a1e945..160edde 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1,165 +1,192 @@ :root { - color: #202124; - background: #f6f7f9; - font-family: - Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + color: #18181b; + background: #f4f4f5; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; line-height: 1.5; -} - -* { - box-sizing: border-box; -} - -body { - margin: 0; - min-width: 320px; + --background: #f4f4f5; + --panel: #ffffff; + --panel-muted: #fafafa; + --border: #d4d4d8; + --text: #18181b; + --muted: #71717a; + --primary: #0f7ae5; + --primary-strong: #0969c8; + --success: #15803d; + --danger: #b91c1c; + --radius: 8px; +} + +* { box-sizing: border-box; } +body { margin: 0; min-width: 320px; min-height: 100vh; } +button, input, textarea, select { font: inherit; } +button { cursor: pointer; } +button:focus-visible, input:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; } + +.login-page { min-height: 100vh; -} - -button, -input, -textarea, -select { - font: inherit; -} - -.shell { display: grid; - grid-template-columns: 280px 1fr; - min-height: 100vh; -} - -.sidebar { - border-right: 1px solid #d9dde3; - background: #ffffff; + place-items: center; padding: 24px; + background: var(--background); } -.sidebar h1 { - margin: 0 0 24px; - font-size: 24px; -} - -.workspace { - display: grid; - gap: 24px; +.login-panel { + width: min(420px, 100%); + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--panel); padding: 24px; } -.connection-status, -.selection-status { - margin: 12px 0 0; - color: #5f6673; - font-size: 13px; -} - -.server-login { +.server-login, +.login-heading, +.channel-page, +.object-inspector, +.channel-sidebar { display: grid; - gap: 8px; -} - -.server-login label { - font-size: 13px; - color: #4b5563; + gap: 12px; } -.server-login input { +.server-login input, +.search-box input { width: 100%; - border: 1px solid #c7ccd4; + border: 1px solid var(--border); border-radius: 6px; padding: 9px 10px; + background: white; } -.server-login button { - border: 1px solid #1f2937; +.server-login button, +.primary-action { + border: 1px solid var(--primary); border-radius: 6px; - background: #1f2937; + background: var(--primary); color: white; - padding: 9px 10px; - cursor: pointer; + padding: 9px 12px; } -.dashboard-grid { +.workbench-shell { + min-height: 100vh; display: grid; - grid-template-columns: repeat(4, minmax(160px, 1fr)); - gap: 12px; + grid-template-rows: 64px 1fr; } -.dashboard-metric { - border: 1px solid #d9dde3; - border-radius: 8px; - background: white; - padding: 16px; +.workspace-topbar { + display: grid; + grid-template-columns: 160px minmax(240px, 1fr) auto; + align-items: center; + gap: 16px; + border-bottom: 1px solid var(--border); + background: var(--panel); + padding: 0 16px; } -.dashboard-metric span { - display: block; - color: #5f6673; - font-size: 14px; +.workbench-body { + min-height: 0; + display: grid; + grid-template-columns: 76px 280px minmax(0, 1fr) 320px; } -.dashboard-metric strong { - display: block; - margin-top: 8px; - font-size: 28px; +.project-rail, +.channel-sidebar, +.object-inspector { + border-right: 1px solid var(--border); + background: var(--panel); } -.inbox-review { +.project-rail { display: grid; - gap: 12px; + align-content: start; + gap: 10px; + padding: 12px; +} + +.project-rail button { + min-width: 48px; + min-height: 48px; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--panel-muted); +} + +.project-rail button.active, +.channel-group button.active { + border-color: var(--primary); + color: var(--primary); + background: #eff6ff; } -.inbox-review h2 { - margin: 0; - font-size: 18px; +.channel-sidebar, +.object-inspector, +.channel-stage { + padding: 16px; + overflow: auto; } -.suggestions { +.channel-group, +.recent-sessions, +.task-list, +.record-list { display: grid; - gap: 10px; + gap: 8px; } -.suggestion { +.channel-group button, +.recent-sessions button, +.task-card, +.record-row, +.cron-row { + width: 100%; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--panel); + padding: 10px 12px; +} + +.task-card, +.record-row, +.cron-row { display: grid; - grid-template-columns: 20px 1fr; - gap: 10px; + grid-template-columns: auto 1fr auto; + gap: 12px; align-items: start; - border: 1px solid #d9dde3; - border-radius: 8px; - background: white; - padding: 12px; } -.suggestion-body { - display: grid; - gap: 4px; +.task-card.completed { + color: var(--muted); + background: var(--panel-muted); } -.suggestion-body small { - color: #5f6673; +.inspector-tabs { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 4px; } -.suggestions button { - justify-self: start; - border: 1px solid #1f2937; - border-radius: 6px; - background: #1f2937; - color: white; - padding: 9px 12px; - cursor: pointer; +.inspector-tabs button[aria-pressed='true'] { + border-color: var(--primary); + color: var(--primary); } -@media (max-width: 760px) { - .shell { +@media (max-width: 920px) { + .workspace-topbar { grid-template-columns: 1fr; + height: auto; + padding: 12px; } - .sidebar { - border-right: 0; - border-bottom: 1px solid #d9dde3; + .workbench-shell { + grid-template-rows: auto 1fr; } - .dashboard-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); + .workbench-body { + grid-template-columns: 1fr; + } + + .project-rail, + .channel-sidebar, + .object-inspector { + border-right: 0; + border-bottom: 1px solid var(--border); } }