## Task 1: Login Page And App-Level Session **Files:** - Modify: `apps/web/src/features/auth/ServerLogin.svelte` - Modify: `apps/web/src/app/App.svelte` - Test: `apps/web/src/features/auth/ServerLogin.test.ts` **Interfaces:** - Produces: `ServerLogin` Svelte component event `login: { apiBase: string; account: string }` - Produces: app state fields `apiBase: string`, `currentUser: { account: string } | null` - Consumes: `localStorage['apiBase']` - [ ] **Step 1: Write failing login component tests** Create `apps/web/src/features/auth/ServerLogin.test.ts`: ```ts import '@testing-library/jest-dom/vitest'; import { fireEvent, render, screen } from '@testing-library/svelte'; import { describe, expect, it, vi } from 'vitest'; import ServerLogin from './ServerLogin.svelte'; describe('ServerLogin', () => { it('renders server, account, and password fields', () => { render(ServerLogin, { props: { apiBase: 'http://localhost:8080' } }); expect(screen.getByLabelText('Server IP or domain')).toBeInTheDocument(); expect(screen.getByLabelText('Email or username')).toBeInTheDocument(); expect(screen.getByLabelText('Password')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument(); }); it('normalizes server address and emits login details', async () => { const onLogin = vi.fn(); render(ServerLogin, { props: { apiBase: 'localhost:8080', onLogin, }, }); await fireEvent.input(screen.getByLabelText('Server IP or domain'), { target: { value: '10.0.0.12:8080' }, }); await fireEvent.input(screen.getByLabelText('Email or username'), { target: { value: 'david@example.com' }, }); await fireEvent.input(screen.getByLabelText('Password'), { target: { value: 'secret' }, }); await fireEvent.click(screen.getByRole('button', { name: 'Log in' })); expect(onLogin).toHaveBeenCalledWith({ apiBase: 'http://10.0.0.12:8080', account: 'david@example.com', }); }); it('shows an error when account or password is missing', async () => { render(ServerLogin, { props: { apiBase: 'http://localhost:8080' } }); await fireEvent.click(screen.getByRole('button', { name: 'Log in' })); expect(screen.getByText('Enter an account and password.')).toBeInTheDocument(); }); }); ``` - [ ] **Step 2: Run the new test to verify it fails** Run: ```powershell Set-Location D:\work\senlinai\agent\apps\web npm test -- --run src/features/auth/ServerLogin.test.ts ``` Expected: FAIL because `ServerLogin` does not render account/password fields and does not expose `onLogin`. - [ ] **Step 3: Replace `ServerLogin.svelte` with the complete login page** Implement `apps/web/src/features/auth/ServerLogin.svelte`: ```svelte
``` - [ ] **Step 4: Update `App.svelte` to switch from login to workbench** Temporarily render a simple logged-in placeholder; the full workbench arrives in Task 3. ```svelte {#if currentUser}

SenlinAI Workbench

Signed in as {currentUser.account}

{:else} {/if} ``` - [ ] **Step 5: Run tests** Run: ```powershell Set-Location D:\work\senlinai\agent\apps\web npm test -- --run src/features/auth/ServerLogin.test.ts ``` Expected: PASS. - [ ] **Step 6: Commit** Run: ```powershell Set-Location D:\work\senlinai\agent git add apps\web\src\features\auth\ServerLogin.svelte apps\web\src\features\auth\ServerLogin.test.ts apps\web\src\app\App.svelte git commit -m "feat: add full workbench login page" ``` ---