## Task 3: Workbench Shell, Project Rail, Channel Sidebar, And Topbar **Files:** - Create: `apps/web/src/features/workbench/ProjectWorkbench.svelte` - Create: `apps/web/src/features/workbench/ProjectRail.svelte` - Create: `apps/web/src/features/workbench/ProjectChannelSidebar.svelte` - Create: `apps/web/src/features/workbench/WorkspaceTopbar.svelte` - Modify: `apps/web/src/app/App.svelte` - Test: `apps/web/src/features/workbench/ProjectWorkbench.test.ts` **Interfaces:** - Consumes: `workbenchProjects: WorkbenchProject[]` - Consumes: `getProjectWorkspace(projectID: number): ProjectWorkspace` - Produces: `ProjectWorkbench` prop `currentUser: { account: string }` - Produces: selected project and selected channel rendered with accessible names. - [ ] **Step 1: Write failing workbench shell test** Create `apps/web/src/features/workbench/ProjectWorkbench.test.ts`: ```ts import '@testing-library/jest-dom/vitest'; import { fireEvent, render, screen } from '@testing-library/svelte'; import { describe, expect, it } from 'vitest'; import ProjectWorkbench from './ProjectWorkbench.svelte'; describe('ProjectWorkbench', () => { it('renders project rail, channel sidebar, topbar, and inspector', () => { render(ProjectWorkbench, { props: { currentUser: { account: 'david@example.com' } } }); expect(screen.getByLabelText('Project list')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Project A1' })).toHaveAttribute('aria-pressed', 'true'); expect(screen.getByLabelText('Project channels')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Message Flow 36' })).toBeInTheDocument(); expect(screen.getByLabelText('Global search')).toBeInTheDocument(); expect(screen.getByLabelText('Object inspector')).toBeInTheDocument(); }); 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(); }); }); ``` - [ ] **Step 2: Run test to verify it fails** Run: ```powershell Set-Location D:\work\senlinai\agent\apps\web npm test -- --run src/features/workbench/ProjectWorkbench.test.ts ``` Expected: FAIL because workbench components do not exist. - [ ] **Step 3: Create shell components** Implement `ProjectRail.svelte`, `ProjectChannelSidebar.svelte`, and `WorkspaceTopbar.svelte` with explicit props and accessible buttons: ```svelte ``` ```svelte ``` ```svelte
SenlinAI
``` - [ ] **Step 4: Create `ProjectWorkbench.svelte` and wire into `App.svelte`** Create `ProjectWorkbench.svelte`: ```svelte

{workspace.channels.find((channel) => channel.id === selectedChannelID)?.title}

``` Modify `App.svelte` to import and render `ProjectWorkbench` when logged in: ```svelte {#if currentUser} {:else} {/if} ``` - [ ] **Step 5: Run test** Run: ```powershell Set-Location D:\work\senlinai\agent\apps\web npm test -- --run src/features/workbench/ProjectWorkbench.test.ts ``` Expected: PASS. - [ ] **Step 6: Commit** Run: ```powershell Set-Location D:\work\senlinai\agent git add apps\web\src\app\App.svelte apps\web\src\features\workbench git commit -m "feat: add project channel workbench shell" ``` ---