42861ea7fa
Multi-adapter mobile UI for AI coding assistants. Supports Claude Code, Codex CLI, and Gemini CLI through one interface. Features: - Real-time bidirectional sync via tmux + WebSocket - Cross-AI review (send one AI's output to another for review) - Multi-review tabs with minimize/expand - Push notifications (PWA) with smart session-aware filtering - Three-channel event system (hooks, file watcher, pane monitor) - Voice input, image paste, draft persistence - Terminal-native design (JetBrains Mono, dark theme, pixel art claw) - CLI with --adapter flag on every command - Zero-overhead fire-and-forget hooks
567 lines
19 KiB
Markdown
567 lines
19 KiB
Markdown
# Send-to Menu Redesign + Settings Page 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:** Redesign the Send-to menu as a two-step bottom sheet with model selection, add a Settings page with saved instructions management and per-adapter preferences.
|
||
|
||
**Architecture:** Three layers of changes: (1) Server — new `saved_instructions` DB table + API endpoints, (2) Client API — new instruction CRUD methods, (3) UI — rewritten ReviewActionMenu, new SettingsView with sub-pages, updated App routing and SessionsView header.
|
||
|
||
**Tech Stack:** React + TypeScript + Vite (client), Express + SQLite + better-sqlite3 (server), Tailwind CSS (styling)
|
||
|
||
**Spec:** `docs/superpowers/specs/2026-03-26-send-to-menu-settings-design.md`
|
||
|
||
---
|
||
|
||
## File Map
|
||
|
||
| File | Action | Responsibility |
|
||
|------|--------|---------------|
|
||
| `server/db.ts` | Modify | Add `saved_instructions` table + prepared statements + `savedInstructions` operations |
|
||
| `server/index.ts` | Modify | Add 3 instruction API endpoints |
|
||
| `src/lib/api.ts` | Modify | Add instruction CRUD client methods |
|
||
| `src/components/ReviewActionMenu.tsx` | Rewrite | Two-step bottom sheet with adapter/model/instructions |
|
||
| `src/components/ChatView.tsx` | Modify | Simplify `handleReviewSelect` — no more context assembly |
|
||
| `src/App.tsx` | Modify | Add `settings` view to routing |
|
||
| `src/components/SessionsView.tsx` | Modify | Add settings icon in header |
|
||
| `src/components/SettingsView.tsx` | Create | Main settings page with sections |
|
||
| `src/components/SavedInstructionsView.tsx` | Create | Instruction list with add/delete |
|
||
| `src/components/AdapterSettingsSection.tsx` | Create | Per-adapter model/permission/effort dropdowns |
|
||
|
||
---
|
||
|
||
## Task 1: Saved Instructions — Server DB + API
|
||
|
||
**Files:**
|
||
- Modify: `server/db.ts`
|
||
- Modify: `server/index.ts`
|
||
|
||
- [ ] **Step 1: Add `saved_instructions` table to DB**
|
||
|
||
In `server/db.ts`, add to the `db.exec()` block (after `session_reviews` table):
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS saved_instructions (
|
||
id TEXT PRIMARY KEY,
|
||
label TEXT NOT NULL,
|
||
instruction TEXT NOT NULL,
|
||
created_at TEXT DEFAULT (datetime('now'))
|
||
);
|
||
```
|
||
|
||
- [ ] **Step 2: Add prepared statements**
|
||
|
||
In the `PreparedStatements` interface, add:
|
||
```typescript
|
||
instructionCreate: BetterSqlite3.Statement;
|
||
instructionGetAll: BetterSqlite3.Statement;
|
||
instructionDelete: BetterSqlite3.Statement;
|
||
```
|
||
|
||
In the `stmts()` function, add:
|
||
```typescript
|
||
instructionCreate: d.prepare(
|
||
`INSERT INTO saved_instructions (id, label, instruction) VALUES (?, ?, ?)`
|
||
),
|
||
instructionGetAll: d.prepare(
|
||
`SELECT * FROM saved_instructions ORDER BY created_at ASC`
|
||
),
|
||
instructionDelete: d.prepare(
|
||
`DELETE FROM saved_instructions WHERE id = ?`
|
||
),
|
||
```
|
||
|
||
- [ ] **Step 3: Add `savedInstructions` export object**
|
||
|
||
After the `sessionReviews` export, add:
|
||
```typescript
|
||
export const savedInstructions = {
|
||
create(id: string, label: string, instruction: string): void {
|
||
stmts().instructionCreate.run(id, label, instruction);
|
||
},
|
||
getAll(): { id: string; label: string; instruction: string; created_at: string }[] {
|
||
return stmts().instructionGetAll.all() as any[];
|
||
},
|
||
delete(id: string): void {
|
||
stmts().instructionDelete.run(id);
|
||
},
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 4: Add API endpoints in `server/index.ts`**
|
||
|
||
Import `savedInstructions` from `./db.js`. Add 3 routes after the review routes:
|
||
|
||
```typescript
|
||
// --- Saved Instructions API ---
|
||
|
||
app.get('/api/instructions', authMiddleware, (_req: Request, res: Response) => {
|
||
try {
|
||
res.json(savedInstructions.getAll());
|
||
} catch (error) {
|
||
res.status(500).json({ error: (error as Error).message });
|
||
}
|
||
});
|
||
|
||
app.post('/api/instructions', authMiddleware, (req: Request, res: Response) => {
|
||
try {
|
||
const { label, instruction } = req.body;
|
||
if (!label || !instruction) return res.status(400).json({ error: 'label and instruction required' });
|
||
const id = randomUUID();
|
||
savedInstructions.create(id, label, instruction);
|
||
res.json({ id, label, instruction });
|
||
} catch (error) {
|
||
res.status(500).json({ error: (error as Error).message });
|
||
}
|
||
});
|
||
|
||
app.delete('/api/instructions/:id', authMiddleware, (req: Request, res: Response) => {
|
||
try {
|
||
savedInstructions.delete(req.params.id);
|
||
res.json({ ok: true });
|
||
} catch (error) {
|
||
res.status(500).json({ error: (error as Error).message });
|
||
}
|
||
});
|
||
```
|
||
|
||
Note: `randomUUID` is already imported in `server/index.ts`.
|
||
|
||
- [ ] **Step 5: Verify TypeScript compiles**
|
||
|
||
Run: `npx tsc --noEmit`
|
||
Expected: No errors
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add server/db.ts server/index.ts
|
||
git commit -m "feat: add saved_instructions DB table and API endpoints"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: Client API — Instruction Methods
|
||
|
||
**Files:**
|
||
- Modify: `src/lib/api.ts`
|
||
|
||
- [ ] **Step 1: Add instruction API methods**
|
||
|
||
Add to the `api` object, following the existing pattern (see `registerReview`, `endReview` for reference):
|
||
|
||
```typescript
|
||
async getInstructions(): Promise<{ id: string; label: string; instruction: string; created_at: string }[]> {
|
||
return request('/api/instructions');
|
||
},
|
||
|
||
async createInstruction(label: string, instruction: string): Promise<{ id: string; label: string; instruction: string }> {
|
||
return request('/api/instructions', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ label, instruction }),
|
||
});
|
||
},
|
||
|
||
async deleteInstruction(id: string): Promise<void> {
|
||
return request(`/api/instructions/${id}`, { method: 'DELETE' });
|
||
},
|
||
```
|
||
|
||
- [ ] **Step 2: Verify TypeScript compiles**
|
||
|
||
Run: `npx tsc --noEmit`
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add src/lib/api.ts
|
||
git commit -m "feat: add instruction CRUD methods to client API"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: ReviewActionMenu — Two-Step Bottom Sheet
|
||
|
||
**Files:**
|
||
- Rewrite: `src/components/ReviewActionMenu.tsx`
|
||
|
||
This is the core UI change. The component goes from a simple template picker to a two-step bottom sheet with adapter selection, model picker, and expandable instructions panel.
|
||
|
||
- [ ] **Step 1: Rewrite ReviewActionMenu.tsx**
|
||
|
||
New props interface:
|
||
```typescript
|
||
interface ReviewActionMenuProps {
|
||
visible: boolean;
|
||
adapters: { id: string; displayName: string }[];
|
||
onDirectSend: (adapter: string, model: string) => void;
|
||
onSendWithInstruction: (adapter: string, model: string, instruction: string, isCustom: boolean) => void;
|
||
onClose: () => void;
|
||
}
|
||
```
|
||
|
||
Component state:
|
||
- `step`: `'adapter' | 'action'` — which step is shown
|
||
- `selectedAdapter`: `string | null` — chosen adapter ID
|
||
- `adapterConfig`: loaded from `api.adapterConfig(selectedAdapter)` when adapter is chosen
|
||
- `selectedModel`: `string` — from adapterConfig.models, default to first item
|
||
- `instructionsExpanded`: `boolean` — toggle for With Instructions section
|
||
- `savedInstructions`: loaded from `api.getInstructions()` on mount
|
||
- `customText`: `string` — free text input value
|
||
|
||
**Step 1 UI** (adapter selection):
|
||
- Backdrop overlay (click to close)
|
||
- Bottom sheet with drag handle
|
||
- "Send to…" title
|
||
- Adapter rows: `<AdapterIcon>` + adapter name, no model text, tap → set selectedAdapter + go to step 2
|
||
|
||
**Step 2 UI** (action selection):
|
||
- `‹ {AdapterName}` header with colored adapter name (back arrow returns to step 1)
|
||
- Model: `<select>` with options from `adapterConfig.models`
|
||
- "Direct Send" button (icon ↗) — calls `onDirectSend(selectedAdapter, selectedModel)` then `onClose()`
|
||
- "With Instructions" button (icon ✎) with ▼/▲ chevron — toggles `instructionsExpanded`
|
||
- When expanded:
|
||
- Saved instructions list (tap → calls `onSendWithInstruction(adapter, model, item.instruction, false)`)
|
||
- Divider "或輸入新的"
|
||
- Text input + ↑ send button → calls `onSendWithInstruction(adapter, model, customText, true)` (isCustom=true triggers save toast)
|
||
- Text input + ↑ send button → calls `onSendWithInstruction(adapter, model, customText)`
|
||
|
||
Use `AdapterIcon` component from `./AdapterIcon` for adapter icons.
|
||
Use `getBrand` from `@/lib/adapter-brands` for adapter colors.
|
||
|
||
Reset `step` to `'adapter'` whenever `visible` changes to true.
|
||
|
||
- [ ] **Step 2: Verify it compiles**
|
||
|
||
Run: `npx tsc --noEmit`
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add src/components/ReviewActionMenu.tsx
|
||
git commit -m "feat: rewrite ReviewActionMenu as two-step bottom sheet"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: ChatView — Simplify Review Flow + Save Toast
|
||
|
||
**Files:**
|
||
- Modify: `src/components/ChatView.tsx`
|
||
|
||
- [ ] **Step 1: Replace handleReviewSelect with two new handlers**
|
||
|
||
Delete the old `handleReviewSelect` callback, the `promptTemplates` record, and the context assembly code.
|
||
|
||
Add two new handlers:
|
||
|
||
```typescript
|
||
const handleDirectSend = useCallback((adapter: string, model: string) => {
|
||
const anchorId = reviewMenuMessageId;
|
||
setReviewMenuMessageId(null);
|
||
setReviewTargetAdapter(null);
|
||
if (!anchorId) return;
|
||
|
||
// Save selected model to adapter prefs so child session uses it
|
||
patchAdapterPrefs(adapter, { model });
|
||
|
||
const anchorMsg = messages.find(m => m.id === anchorId);
|
||
const rawText = anchorMsg ? extractTextFromBlocks(anchorMsg.content) : '';
|
||
|
||
setHistoryReview(null);
|
||
setActiveReview({
|
||
reviewId: '', childSessionId: '', childCliSessionId: '',
|
||
childAdapter: adapter, anchorMessageId: anchorId, reviewTitle: 'direct',
|
||
});
|
||
setReviewInitialPrompt(rawText);
|
||
setReviewCwd(cwd || null);
|
||
setActiveReviewPanel('expanded');
|
||
}, [reviewMenuMessageId, messages, cwd]);
|
||
|
||
const handleSendWithInstruction = useCallback((adapter: string, model: string, instruction: string, isCustom: boolean) => {
|
||
const anchorId = reviewMenuMessageId;
|
||
setReviewMenuMessageId(null);
|
||
setReviewTargetAdapter(null);
|
||
if (!anchorId) return;
|
||
|
||
// Save selected model to adapter prefs so child session uses it
|
||
patchAdapterPrefs(adapter, { model });
|
||
|
||
const anchorMsg = messages.find(m => m.id === anchorId);
|
||
const rawText = anchorMsg ? extractTextFromBlocks(anchorMsg.content) : '';
|
||
const prompt = `${instruction}\n\n${rawText}`;
|
||
const title = instruction.substring(0, 30);
|
||
|
||
setHistoryReview(null);
|
||
setActiveReview({
|
||
reviewId: '', childSessionId: '', childCliSessionId: '',
|
||
childAdapter: adapter, anchorMessageId: anchorId, reviewTitle: title,
|
||
});
|
||
setReviewInitialPrompt(prompt);
|
||
setReviewCwd(cwd || null);
|
||
setActiveReviewPanel('expanded');
|
||
|
||
// Show save toast only for custom (typed) instructions, not saved ones
|
||
if (isCustom) {
|
||
setSaveToast({ instruction, label: title });
|
||
setTimeout(() => setSaveToast(null), 3000);
|
||
}
|
||
}, [reviewMenuMessageId, messages, cwd]);
|
||
```
|
||
|
||
Add state:
|
||
```typescript
|
||
const [saveToast, setSaveToast] = useState<{ instruction: string; label: string } | null>(null);
|
||
```
|
||
|
||
- [ ] **Step 2: Update ReviewActionMenu JSX**
|
||
|
||
Replace the current `<ReviewActionMenu>` with:
|
||
```tsx
|
||
<ReviewActionMenu
|
||
visible={!!reviewMenuMessageId}
|
||
adapters={sendTargets.map(t => ({ id: t.adapter, displayName: t.label }))}
|
||
onDirectSend={handleDirectSend}
|
||
onSendWithInstruction={handleSendWithInstruction}
|
||
onClose={() => { setReviewMenuMessageId(null); setReviewTargetAdapter(null); }}
|
||
/>
|
||
```
|
||
|
||
- [ ] **Step 3: Add save toast UI**
|
||
|
||
Render at the bottom of the ChatView return, before the closing `</div>`:
|
||
```tsx
|
||
{saveToast && (
|
||
<div className="fixed bottom-20 left-4 right-4 bg-[#1a1a1a] border border-[#333] rounded-xl p-3 flex items-center justify-between z-30">
|
||
<span className="text-sm text-[#888]">存成常用?</span>
|
||
<button
|
||
className="text-sm text-blue-400 font-medium px-3 py-1 rounded-lg hover:bg-blue-400/10"
|
||
onClick={() => {
|
||
api.createInstruction(saveToast.label, saveToast.instruction);
|
||
setSaveToast(null);
|
||
}}
|
||
>Save</button>
|
||
</div>
|
||
)}
|
||
```
|
||
|
||
- [ ] **Step 4: Verify TypeScript compiles**
|
||
|
||
Run: `npx tsc --noEmit`
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add src/components/ChatView.tsx
|
||
git commit -m "feat: simplify review flow — direct send raw text, instructions without context"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: App Routing — Add Settings View
|
||
|
||
**Files:**
|
||
- Modify: `src/App.tsx`
|
||
|
||
- [ ] **Step 1: Add settings to View type and rendering**
|
||
|
||
Add `| { name: 'settings' }` to the `View` union type.
|
||
|
||
Add rendering branch before the default `SessionsView`:
|
||
```tsx
|
||
if (view.name === 'settings') {
|
||
return <SettingsView onBack={() => setView({ name: 'sessions' })} />;
|
||
}
|
||
```
|
||
|
||
Add import: `import { SettingsView } from './components/SettingsView';`
|
||
|
||
- [ ] **Step 2: Pass onOpenSettings to SessionsView**
|
||
|
||
Add prop to the `<SessionsView>` JSX:
|
||
```tsx
|
||
onOpenSettings={() => setView({ name: 'settings' })}
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add src/App.tsx
|
||
git commit -m "feat: add settings view to app routing"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6: SessionsView — Settings Icon in Header
|
||
|
||
**Files:**
|
||
- Modify: `src/components/SessionsView.tsx`
|
||
|
||
- [ ] **Step 1: Add `onOpenSettings` prop and gear icon**
|
||
|
||
Add `onOpenSettings: () => void` to the component props.
|
||
|
||
In the header's button group (the `<div className="flex items-center gap-2">` block), add before the Logout button:
|
||
```tsx
|
||
<button
|
||
onClick={onOpenSettings}
|
||
className="p-2 text-text-dim hover:text-text transition-colors"
|
||
title="Settings"
|
||
>
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||
<circle cx="12" cy="12" r="3"/>
|
||
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/>
|
||
</svg>
|
||
</button>
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add src/components/SessionsView.tsx
|
||
git commit -m "feat: add settings gear icon to project list header"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 7: SettingsView — Main Settings Page
|
||
|
||
**Files:**
|
||
- Create: `src/components/SettingsView.tsx`
|
||
|
||
- [ ] **Step 1: Create SettingsView component**
|
||
|
||
Props: `onBack: () => void`
|
||
|
||
State:
|
||
- `subView`: `'main' | 'instructions' | string` (string = adapter id)
|
||
- `adapters`: fetched from `api.adapters()` on mount
|
||
- `version`: fetched from server health endpoint or read from a constant
|
||
|
||
Sub-view routing:
|
||
- `subView === 'instructions'` → render `<SavedInstructionsView onBack={() => setSubView('main')} />`
|
||
- `subView` matches an adapter id → render `<AdapterSettingsSection adapter={subView} onBack={() => setSubView('main')} />`
|
||
- Default → render main list
|
||
|
||
Main list: dark themed rows with chevrons, each tappable:
|
||
- "Saved Instructions" → `setSubView('instructions')`
|
||
- One row per adapter (icon + name) → `setSubView(adapterId)`
|
||
- "Notifications" → inline push toggle (reuse existing `usePushNotifications` hook)
|
||
- "About" → show `CodeTap v{version}` inline
|
||
|
||
Header: back arrow + "Settings" title, same style as existing headers.
|
||
|
||
- [ ] **Step 2: Verify TypeScript compiles**
|
||
|
||
Run: `npx tsc --noEmit`
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add src/components/SettingsView.tsx
|
||
git commit -m "feat: create SettingsView with section navigation"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 8: SavedInstructionsView — Instruction Management
|
||
|
||
**Files:**
|
||
- Create: `src/components/SavedInstructionsView.tsx`
|
||
|
||
- [ ] **Step 1: Create SavedInstructionsView component**
|
||
|
||
Props: `onBack: () => void`
|
||
|
||
State:
|
||
- `instructions`: array, fetched from `api.getInstructions()` on mount
|
||
- `showAddForm`: boolean
|
||
- `newLabel`: string
|
||
- `newInstruction`: string
|
||
|
||
UI:
|
||
- Header: back arrow + "Saved Instructions" + `[+ Add]` button
|
||
- List: each item shows label (bold) + instruction preview (truncated, dim) + ✕ delete button
|
||
- Delete: show `window.confirm('Delete this instruction?')`, then `api.deleteInstruction(id)`, remove from local state
|
||
- Add form (when showAddForm): label input + instruction textarea + [Save] + [Cancel] buttons
|
||
- Save: `api.createInstruction(label, instruction)`, append to local state, hide form
|
||
|
||
- [ ] **Step 2: Verify TypeScript compiles**
|
||
|
||
Run: `npx tsc --noEmit`
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add src/components/SavedInstructionsView.tsx
|
||
git commit -m "feat: create SavedInstructionsView with add/delete"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 9: AdapterSettingsSection — Per-Adapter Preferences
|
||
|
||
**Files:**
|
||
- Create: `src/components/AdapterSettingsSection.tsx`
|
||
|
||
- [ ] **Step 1: Create AdapterSettingsSection component**
|
||
|
||
Props: `adapter: string; onBack: () => void`
|
||
|
||
State:
|
||
- `config`: fetched from `api.adapterConfig(adapter)` on mount (contains models, permissionModes, effortLevels, effortLabel)
|
||
- `prefs`: loaded from `loadAdapterPrefs(adapter)` (model, permissionMode, effort)
|
||
|
||
UI:
|
||
- Header: back arrow + AdapterIcon + adapter display name (from `getBrand`)
|
||
- Three `<select>` dropdowns, each with a label:
|
||
- "Model" → options from `config.models` (each has `value` + `label`)
|
||
- "Permission Mode" → options from `config.permissionModes`
|
||
- Effort label from `config.effortLabel` (e.g. "Thinking" for Claude, "Effort" for Codex) → options from `config.effortLevels`
|
||
- On change: `patchAdapterPrefs(adapter, { [field]: value })` to persist to localStorage
|
||
|
||
Import `loadAdapterPrefs`, `patchAdapterPrefs` from `@/lib/adapter-prefs`.
|
||
Import `getBrand` from `@/lib/adapter-brands`.
|
||
Import `AdapterIcon` from `./AdapterIcon`.
|
||
|
||
- [ ] **Step 2: Verify TypeScript compiles**
|
||
|
||
Run: `npx tsc --noEmit`
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add src/components/AdapterSettingsSection.tsx
|
||
git commit -m "feat: create AdapterSettingsSection with per-adapter dropdowns"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 10: E2E Verification
|
||
|
||
- [ ] **Step 1: Full TypeScript check**
|
||
|
||
Run: `npx tsc --noEmit`
|
||
Expected: No errors
|
||
|
||
- [ ] **Step 2: Start server (without watch mode) and Vite**
|
||
|
||
Start server and Vite in separate processes. Ensure tmux session exists first.
|
||
|
||
- [ ] **Step 3: Visual verification in browser**
|
||
|
||
Verify the following scenarios:
|
||
1. Settings icon visible in project list header
|
||
2. Settings page loads with all sections
|
||
3. Saved Instructions: add an instruction, verify it appears in list, delete it
|
||
4. Adapter settings: all dropdowns populate with correct adapter-specific options
|
||
5. In a chat, click send icon → two-step menu opens
|
||
6. Step 1 shows adapter icons + names (no model text)
|
||
7. Step 2 shows model dropdown + Direct Send + With Instructions
|
||
8. With Instructions has expand/collapse chevron
|
||
9. Direct Send sends only raw response text
|
||
10. With Instructions sends instruction + raw text
|
||
11. Save toast appears and works after custom instruction send
|
||
|
||
- [ ] **Step 4: Final commit if any fixes needed**
|