# Discord Style Project Workbench UI Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build the confirmed shadcn/ui-inspired Svelte workbench UI with a full login page, dynamic project rail, project-scoped channel sidebar, channel-specific content pages, and a right-side object inspector.
**Architecture:** Keep the implementation frontend-focused under `apps/web/src`. `App.svelte` owns authentication state and switches between login and workbench. `ProjectWorkbench.svelte` owns selected project, selected channel, selected object, and passes focused data into small channel components. Mock project/channel data lives in a local typed data module so later backend API wiring can replace it without reshaping every component.
**Tech Stack:** Svelte 5, TypeScript, Vite, Vitest, Testing Library for Svelte, Playwright, local CSS. Visual style follows root `design.md`; do not add React or shadcn/ui runtime dependencies.
## Global Constraints
- Frontend only uses Svelte; do not add React dependencies or React components.
- UI can visually reference shadcn/ui, but must not add shadcn/ui as a dependency.
- Web app is desktop-first while remaining usable on mobile browsers.
- Feature code lives under `apps/web/src/features`.
- Login page must allow users to enter server IP address or domain.
- Browser-style workspace state is separate from editor drafts and AI input drafts.
- MVP uses Markdown for note editing.
- Project tags remain project-scoped; do not introduce global tags.
- AI may not create official objects without user confirmation.
- Cron plan UI is plan/reminder management only; do not implement autonomous Agent execution.
- Custom channels are URL shortcuts with title, icon, URL, and sort order.
- Web verification runs from `apps/web`: `npm test -- --run`, `npm run build`, and Playwright when main workflow changes.
---
## File Structure
Create or modify these files:
- Modify: `apps/web/src/app/App.svelte`
Owns app-level login/workbench switching, persisted API base URL, and mock login session.
- Modify: `apps/web/src/features/auth/ServerLogin.svelte`
Becomes a complete login page with server address, account, password, submit, and error/connection state.
- Create: `apps/web/src/features/workbench/types.ts`
Defines `WorkbenchProject`, `WorkbenchChannel`, `ChannelType`, channel object types, inspector model, and mock item interfaces.
- Create: `apps/web/src/features/workbench/mockData.ts`
Provides typed dynamic projects, project-specific system channels, custom URL channels, tags, recent sessions, inbox messages, tasks, notes/sources, cron plans, and AI sessions.
- Create: `apps/web/src/features/workbench/ProjectWorkbench.svelte`
Main logged-in shell with topbar, project rail, channel sidebar, main channel content, and inspector.
- Create: `apps/web/src/features/workbench/ProjectRail.svelte`
Dynamic project list with active project state and create-project action.
- Create: `apps/web/src/features/workbench/ProjectChannelSidebar.svelte`
Project-scoped channel list, tags, custom channels, recent sessions, and counts.
- Create: `apps/web/src/features/workbench/WorkspaceTopbar.svelte`
Logo, global search, navigation actions, account action.
- Create: `apps/web/src/features/workbench/ChannelContent.svelte`
Dispatches selected channel to the matching channel component.
- Create: `apps/web/src/features/workbench/ObjectInspector.svelte`
Right panel with Discussion, Properties, and More tabs.
- Create: `apps/web/src/features/workbench/channels/OverviewChannel.svelte`
- Create: `apps/web/src/features/workbench/channels/InboxChannel.svelte`
- Create: `apps/web/src/features/workbench/channels/TasksChannel.svelte`
- Create: `apps/web/src/features/workbench/channels/AISessionsChannel.svelte`
- Create: `apps/web/src/features/workbench/channels/NotesSourcesChannel.svelte`
- Create: `apps/web/src/features/workbench/channels/CronChannel.svelte`
- Create: `apps/web/src/features/workbench/channels/CustomLinkChannel.svelte`
- Create: `apps/web/src/features/workbench/ProjectWorkbench.test.ts`
- Modify: `apps/web/e2e/project-workbench.spec.ts`
- Modify: `apps/web/src/index.css`
---
## 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
SA
Private workbench
SenlinAI Workbench
```
- [ ] **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}