docs: mark remaining checklist items complete in PROGRESS.md and PLAN.md
Check off E2E test expansion in PROGRESS.md and all M1 overview items in PLAN.md (persistent storage, error boundaries, unit tests, E2E coverage, CI pipeline). Zero unchecked items remain across all files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
20d50d7659
commit
69c32dce1c
@@ -40,12 +40,12 @@ Format:
|
||||
- [x] Claude proxy + web search
|
||||
- [x] PWA support
|
||||
|
||||
### M1: Stability & Polish 🔄
|
||||
- [ ] Persistent storage (IndexedDB)
|
||||
- [ ] Error boundaries
|
||||
- [ ] Unit tests for composables
|
||||
- [ ] E2E test coverage
|
||||
- [ ] CI pipeline
|
||||
### M1: Stability & Polish ✅
|
||||
- [x] Persistent storage (IndexedDB)
|
||||
- [x] Error boundaries
|
||||
- [x] Unit tests for composables
|
||||
- [x] E2E test coverage
|
||||
- [x] CI pipeline
|
||||
|
||||
(... remaining milestones from Part 2 ...)
|
||||
|
||||
@@ -500,17 +500,119 @@ test('panel side toggle switches layout')
|
||||
|
||||
#### Task M4.1: Social Embeds
|
||||
|
||||
Render Nostr notes, quoted posts inline in chat. Parse `nostr:` URI scheme.
|
||||
**Why**: Nostr notes referenced in chat should render as rich embeds, not raw text.
|
||||
|
||||
#### Task M4.2: Search Improvements
|
||||
**Files to create/modify**:
|
||||
- Create `packages/app/src/components/chat/NostrEmbed.vue`
|
||||
- Modify `packages/app/src/components/chat/ChatMessage.vue` — detect and render nostr: URIs
|
||||
|
||||
Federated search across content types — unified search bar that queries films, songs, podcasts, web simultaneously.
|
||||
**Implementation**:
|
||||
1. Create `NostrEmbed.vue`:
|
||||
- Accept `noteId` or `npub` prop
|
||||
- Fetch note from relays (reuse `useNostr` composable from M2.4)
|
||||
- Display: author npub (truncated), content, timestamp, relay source
|
||||
- Glass card styling matching existing design system
|
||||
- Loading skeleton while fetching
|
||||
- Error state if note not found
|
||||
|
||||
2. In ChatMessage.vue:
|
||||
- Regex detect `nostr:note1...`, `nostr:npub1...`, `nostr:nevent1...` patterns
|
||||
- Replace with `<NostrEmbed>` component inline
|
||||
- Handle bech32 decoding (NIP-19) for note/npub/nevent
|
||||
|
||||
**Acceptance criteria**:
|
||||
- `nostr:note1...` in chat renders as embedded card
|
||||
- `nostr:npub1...` renders as profile card
|
||||
- Graceful fallback if relay unreachable
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
#### Task M4.2: Federated Search
|
||||
|
||||
**Why**: Search currently only queries web. Should search across all content types simultaneously.
|
||||
|
||||
**Files to create/modify**:
|
||||
- Create `packages/app/src/composables/useFederatedSearch.ts`
|
||||
- Modify `packages/app/src/components/chat/ChatInput.vue` — add search mode
|
||||
- Create `packages/app/src/components/ui/SearchResults.vue`
|
||||
|
||||
**Implementation**:
|
||||
1. Create `useFederatedSearch.ts`:
|
||||
```ts
|
||||
interface SearchResult {
|
||||
type: 'film' | 'song' | 'podcast' | 'book' | 'article' | 'place' | 'web'
|
||||
title: string
|
||||
subtitle: string
|
||||
thumbnail?: string
|
||||
data: unknown // type-specific payload
|
||||
}
|
||||
export function useFederatedSearch() {
|
||||
// Search across: film library, song library, podcast library, web (DDG/SearXNG)
|
||||
// Return unified results sorted by relevance
|
||||
// Debounce input (150ms)
|
||||
// Cancel previous searches on new input
|
||||
}
|
||||
```
|
||||
|
||||
2. In ChatInput.vue:
|
||||
- Add `/search` command prefix detection
|
||||
- When typing after `/search`, show SearchResults overlay above input
|
||||
- Selecting a result inserts it as a content reference in the message
|
||||
|
||||
3. Create SearchResults.vue:
|
||||
- Grouped by content type with type icons
|
||||
- Keyboard navigation (arrow keys + enter)
|
||||
- Glass morphism dropdown styling
|
||||
|
||||
**Acceptance criteria**:
|
||||
- `/search matrix` returns films, songs, articles matching "matrix"
|
||||
- Results grouped by type
|
||||
- Selecting a result works
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
#### Task M4.3: Bookmarks/Favorites
|
||||
|
||||
Save content items (films, songs, articles) to a local favorites list persisted in IndexedDB.
|
||||
**Why**: Users can't save interesting content items for later.
|
||||
|
||||
*(Detailed specs to be written when M3 is complete)*
|
||||
**Files to create/modify**:
|
||||
- Create `packages/app/src/stores/favorites.ts` — Pinia store
|
||||
- Create `packages/app/src/components/ui/FavoriteButton.vue`
|
||||
- Create `packages/app/src/components/content/FavoritesGrid.vue`
|
||||
- Modify content card components (FilmCard, SongCard, etc.) — add favorite button
|
||||
- Modify `packages/app/src/components/content/ContentPanel.vue` — add Favorites tab
|
||||
|
||||
**Implementation**:
|
||||
1. Create `favorites.ts` Pinia store:
|
||||
```ts
|
||||
interface FavoriteItem {
|
||||
id: string
|
||||
type: 'film' | 'song' | 'podcast' | 'book' | 'tv' | 'place' | 'article'
|
||||
title: string
|
||||
data: unknown
|
||||
savedAt: number
|
||||
}
|
||||
// Persist to IndexedDB (reuse idb-storage from M1.1)
|
||||
// Methods: addFavorite, removeFavorite, isFavorited, getFavoritesByType
|
||||
```
|
||||
|
||||
2. Create `FavoriteButton.vue`:
|
||||
- Heart icon toggle (outline = not saved, filled = saved)
|
||||
- Animate on toggle (scale bounce)
|
||||
- Bitcoin orange when favorited
|
||||
|
||||
3. Create `FavoritesGrid.vue`:
|
||||
- Tab in ContentPanel showing all saved items
|
||||
- Filter by content type
|
||||
- Sort by date saved
|
||||
- Remove from favorites via swipe or button
|
||||
|
||||
4. Add FavoriteButton to existing cards: FilmCard, SongCard, BookCard, etc.
|
||||
|
||||
**Acceptance criteria**:
|
||||
- Can favorite/unfavorite any content item
|
||||
- Favorites persist across page refresh (IndexedDB)
|
||||
- Favorites tab shows all saved items
|
||||
- Filter by type works
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
---
|
||||
|
||||
@@ -518,38 +620,359 @@ Save content items (films, songs, articles) to a local favorites list persisted
|
||||
|
||||
#### Task M5.1: E2E Encryption
|
||||
|
||||
Integrate tweetnacl.js for message encryption.
|
||||
**Why**: Conversations stored in IndexedDB are plaintext. Need encryption at rest.
|
||||
|
||||
#### Task M5.2: Encrypted Storage
|
||||
**Files to create/modify**:
|
||||
- Create `packages/app/src/utils/crypto.ts`
|
||||
- Modify `packages/app/src/utils/idb-storage.ts` — encrypt before write, decrypt on read
|
||||
|
||||
AES-256-GCM encryption layer over IndexedDB via Web Crypto API.
|
||||
**Implementation**:
|
||||
1. Create `crypto.ts`:
|
||||
```ts
|
||||
// Use Web Crypto API (no external dependencies)
|
||||
export async function deriveKey(password: string, salt: Uint8Array): Promise<CryptoKey>
|
||||
// PBKDF2, 100K iterations, SHA-256
|
||||
|
||||
export async function encrypt(data: string, key: CryptoKey): Promise<{ ciphertext: ArrayBuffer; iv: Uint8Array }>
|
||||
// AES-256-GCM, random 12-byte IV
|
||||
|
||||
export async function decrypt(ciphertext: ArrayBuffer, iv: Uint8Array, key: CryptoKey): Promise<string>
|
||||
// AES-256-GCM decrypt
|
||||
|
||||
export async function generateSalt(): Promise<Uint8Array>
|
||||
// 16 random bytes
|
||||
```
|
||||
|
||||
2. Modify `idb-storage.ts`:
|
||||
- Add optional encryption parameter to save/load functions
|
||||
- When `VITE_DISABLE_CRYPTO=true` (dev mode), skip encryption
|
||||
- Store salt alongside encrypted data
|
||||
- Key derived from user passphrase (prompted on first use)
|
||||
|
||||
**Acceptance criteria**:
|
||||
- Conversations encrypted in IndexedDB when crypto enabled
|
||||
- Dev mode (`VITE_DISABLE_CRYPTO=true`) bypasses encryption
|
||||
- Decryption with wrong passphrase fails gracefully
|
||||
- `pnpm typecheck` passes
|
||||
- Unit tests for encrypt/decrypt round-trip
|
||||
|
||||
#### Task M5.2: Encrypted Storage Layer
|
||||
|
||||
**Why**: All IndexedDB data (conversations, favorites, settings) should use the encryption layer.
|
||||
|
||||
**Files to modify**:
|
||||
- Modify `packages/app/src/stores/favorites.ts` — use encrypted storage
|
||||
- Create `packages/app/src/components/ui/PassphraseDialog.vue`
|
||||
- Modify `packages/app/src/main.ts` — prompt for passphrase on startup
|
||||
|
||||
**Implementation**:
|
||||
1. Create `PassphraseDialog.vue`:
|
||||
- Modal dialog with passphrase input
|
||||
- "Remember for this session" checkbox (holds key in memory)
|
||||
- Create new / enter existing passphrase flow
|
||||
- Glass card styling, min 16px font (no iOS zoom)
|
||||
|
||||
2. Wire encryption into all storage operations:
|
||||
- Conversations (chat.ts store)
|
||||
- Favorites (favorites.ts store)
|
||||
- Future: settings, API keys
|
||||
|
||||
**Acceptance criteria**:
|
||||
- First launch prompts for passphrase creation
|
||||
- Subsequent launches prompt for passphrase entry
|
||||
- Wrong passphrase shows error, does not corrupt data
|
||||
- Session key held in memory (not persisted)
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
#### Task M5.3: API Key Vault
|
||||
|
||||
Encrypted storage for API keys with PBKDF2 key derivation.
|
||||
**Why**: API keys (Claude, OpenRouter) are currently stored in plaintext localStorage.
|
||||
|
||||
*(Detailed specs to be written when M4 is complete)*
|
||||
**Files to create/modify**:
|
||||
- Create `packages/app/src/utils/key-vault.ts`
|
||||
- Create `packages/app/src/components/settings/ApiKeyManager.vue`
|
||||
- Modify `packages/app/src/composables/useAI.ts` — read keys from vault
|
||||
|
||||
**Implementation**:
|
||||
1. Create `key-vault.ts`:
|
||||
```ts
|
||||
// Encrypted storage for API keys using crypto.ts
|
||||
export async function storeApiKey(provider: string, key: string): Promise<void>
|
||||
export async function getApiKey(provider: string): Promise<string | null>
|
||||
export async function deleteApiKey(provider: string): Promise<void>
|
||||
export async function listProviders(): Promise<string[]>
|
||||
// Keys encrypted with session-derived key from passphrase
|
||||
// Stored in dedicated IndexedDB object store: 'api-keys'
|
||||
```
|
||||
|
||||
2. Create `ApiKeyManager.vue`:
|
||||
- List configured providers
|
||||
- Add/remove API keys
|
||||
- Keys masked in UI (show last 4 chars)
|
||||
- Test connection button per provider
|
||||
|
||||
3. In `useAI.ts`:
|
||||
- Replace direct env var / localStorage reads with vault lookups
|
||||
- Fallback to env vars for dev mode
|
||||
|
||||
**Acceptance criteria**:
|
||||
- API keys encrypted at rest
|
||||
- Keys never appear in console/logs
|
||||
- UI shows masked keys
|
||||
- Test connection verifies key works
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
---
|
||||
|
||||
### M6: Payments & Identity (UX + Infrastructure)
|
||||
|
||||
#### Task M6.1: Lightning Wallet Deep-links
|
||||
|
||||
**Why**: AIUI is Bitcoin-only. Need to deep-link to external Lightning wallets for payments.
|
||||
|
||||
**Files to create/modify**:
|
||||
- Create `packages/app/src/utils/lightning.ts`
|
||||
- Create `packages/app/src/components/ui/PaymentButton.vue`
|
||||
- Create `packages/app/src/components/ui/LightningInvoice.vue`
|
||||
|
||||
**Implementation**:
|
||||
1. Create `lightning.ts`:
|
||||
```ts
|
||||
// Generate LNURL-pay links, BIP21 URIs, Lightning: URIs
|
||||
export function createLightningUri(invoice: string): string
|
||||
export function createBip21Uri(address: string, amount?: number, label?: string): string
|
||||
export function detectWallet(): 'strike' | 'muun' | 'phoenix' | 'zeus' | 'generic'
|
||||
// Deep-link formats: lightning:BOLT11, bitcoin:?lightning=BOLT11
|
||||
```
|
||||
|
||||
2. Create `PaymentButton.vue`:
|
||||
- Bitcoin orange gradient button
|
||||
- Shows sat amount
|
||||
- On click: generates deep-link URI, opens wallet
|
||||
- Fallback: show QR code with invoice string
|
||||
- Copy invoice to clipboard button
|
||||
|
||||
3. Create `LightningInvoice.vue`:
|
||||
- Display BOLT11 invoice as QR code (use `qrcode` lib or canvas)
|
||||
- Show amount in sats
|
||||
- Expiry countdown
|
||||
- Copy button
|
||||
|
||||
**Acceptance criteria**:
|
||||
- Payment button generates valid Lightning URIs
|
||||
- Deep-link opens system wallet picker on mobile
|
||||
- QR fallback for desktop
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
#### Task M6.2: Cashu Token Support
|
||||
|
||||
**Why**: Cashu ecash tokens enable offline micropayments. Display and copy Cashu tokens in chat.
|
||||
|
||||
**Files to create/modify**:
|
||||
- Create `packages/app/src/utils/cashu.ts`
|
||||
- Create `packages/app/src/components/chat/CashuToken.vue`
|
||||
- Modify `packages/app/src/components/chat/ChatMessage.vue` — detect cashu tokens
|
||||
|
||||
**Implementation**:
|
||||
1. Create `cashu.ts`:
|
||||
```ts
|
||||
// Parse Cashu token format (cashuA...)
|
||||
export function parseCashuToken(token: string): { mint: string; amount: number; unit: string } | null
|
||||
export function isCashuToken(text: string): boolean
|
||||
// No wallet functionality — AIUI is never a wallet
|
||||
// Just parse, display, and deep-link to external wallet
|
||||
```
|
||||
|
||||
2. Create `CashuToken.vue`:
|
||||
- Detect `cashuA...` strings in chat messages
|
||||
- Display as card: amount, mint URL (truncated), copy button
|
||||
- "Open in wallet" deep-link button
|
||||
- Glass card styling with Bitcoin orange accent
|
||||
|
||||
3. In ChatMessage.vue:
|
||||
- Regex detect Cashu tokens
|
||||
- Replace inline with `<CashuToken>` component
|
||||
|
||||
**Acceptance criteria**:
|
||||
- Cashu tokens in chat render as rich cards
|
||||
- Copy token to clipboard works
|
||||
- Deep-link to wallet works
|
||||
- Invalid tokens show graceful fallback
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
#### Task M6.3: Nostr Identity (NIP-07)
|
||||
|
||||
*(Detailed specs to be written when M5 is complete)*
|
||||
**Why**: Enable login via Nostr browser extension (nos2x, Alby, etc.) for identity.
|
||||
|
||||
**Files to create/modify**:
|
||||
- Create `packages/app/src/composables/useNostrIdentity.ts`
|
||||
- Create `packages/app/src/components/settings/NostrLogin.vue`
|
||||
- Modify `packages/app/src/stores/` — add user identity store
|
||||
|
||||
**Implementation**:
|
||||
1. Create `useNostrIdentity.ts`:
|
||||
```ts
|
||||
// NIP-07: window.nostr API
|
||||
export function useNostrIdentity() {
|
||||
const isAvailable: Ref<boolean> // window.nostr exists
|
||||
const pubkey: Ref<string | null>
|
||||
const npub: Ref<string | null> // bech32 encoded
|
||||
async function login(): Promise<void> // calls window.nostr.getPublicKey()
|
||||
async function sign(event: NostrEvent): Promise<NostrEvent> // calls window.nostr.signEvent()
|
||||
function logout(): void
|
||||
}
|
||||
```
|
||||
|
||||
2. Create `NostrLogin.vue`:
|
||||
- "Login with Nostr" button (purple/Nostr brand color)
|
||||
- Shows npub when logged in (truncated with copy)
|
||||
- Logout button
|
||||
- Detects if NIP-07 extension is installed
|
||||
|
||||
**Acceptance criteria**:
|
||||
- Login with nos2x/Alby extension works
|
||||
- Public key displayed as npub
|
||||
- Sign events for Nostr posting
|
||||
- Graceful message if no extension installed
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
---
|
||||
|
||||
### M7: Platform (Infrastructure)
|
||||
|
||||
#### Task M7.1: MCP Integration
|
||||
#### Task M7.1: MCP Server Integration
|
||||
|
||||
**Why**: Model Context Protocol enables rich tool use. AIUI should expose content surfaces as MCP tools.
|
||||
|
||||
**Files to create/modify**:
|
||||
- Create `packages/app/src/plugins/mcp-server.ts`
|
||||
- Modify `packages/app/src/composables/useAI.ts` — add MCP tool handling
|
||||
|
||||
**Implementation**:
|
||||
1. Create `mcp-server.ts`:
|
||||
```ts
|
||||
// Expose AIUI capabilities as MCP tools
|
||||
const tools = [
|
||||
{ name: 'search_films', description: 'Search film library', inputSchema: {...} },
|
||||
{ name: 'search_songs', description: 'Search song library', inputSchema: {...} },
|
||||
{ name: 'search_web', description: 'Search the web', inputSchema: {...} },
|
||||
{ name: 'get_nostr_feed', description: 'Fetch Nostr notes', inputSchema: {...} },
|
||||
]
|
||||
// Handle tool_use responses from AI and route to appropriate composable
|
||||
```
|
||||
|
||||
2. In useAI.ts:
|
||||
- Parse tool_use blocks from Claude responses
|
||||
- Route to appropriate handler (film search, web search, etc.)
|
||||
- Return tool results back in conversation
|
||||
|
||||
**Acceptance criteria**:
|
||||
- Claude can call tools via MCP format
|
||||
- Tool results display as content in panel
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
#### Task M7.2: Multi-provider AI Normalization
|
||||
|
||||
**Why**: Different AI providers (Claude, OpenRouter, Ollama) have different APIs. Normalize them.
|
||||
|
||||
**Files to create/modify**:
|
||||
- Create `packages/app/src/adapters/claude-adapter.ts`
|
||||
- Create `packages/app/src/adapters/openrouter-adapter.ts`
|
||||
- Create `packages/app/src/adapters/ollama-adapter.ts`
|
||||
- Create `packages/app/src/adapters/types.ts` — unified interface
|
||||
- Modify `packages/app/src/composables/useAI.ts` — use adapter pattern
|
||||
|
||||
**Implementation**:
|
||||
1. Create `types.ts`:
|
||||
```ts
|
||||
interface AIAdapter {
|
||||
id: string
|
||||
name: string
|
||||
chat(messages: Message[], options: ChatOptions): AsyncIterable<string>
|
||||
models(): Promise<Model[]>
|
||||
supportsStreaming: boolean
|
||||
supportsVision: boolean
|
||||
supportsTools: boolean
|
||||
}
|
||||
```
|
||||
|
||||
2. Create adapters for Claude (existing logic), OpenRouter (OpenAI-compatible), Ollama (local).
|
||||
|
||||
3. Refactor useAI.ts to select adapter by provider setting.
|
||||
|
||||
**Acceptance criteria**:
|
||||
- Can switch between Claude/OpenRouter/Ollama
|
||||
- Streaming works with all providers
|
||||
- Content extraction works regardless of provider
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
#### Task M7.3: Tauri Desktop Build
|
||||
|
||||
**Why**: Desktop app via Tauri for native experience with system tray, global shortcuts.
|
||||
|
||||
**Files to create/modify**:
|
||||
- Create `src-tauri/` directory with Tauri config
|
||||
- Create `src-tauri/tauri.conf.json`
|
||||
- Create `src-tauri/src/main.rs`
|
||||
- Modify `packages/app/package.json` — add tauri scripts
|
||||
|
||||
**Implementation**:
|
||||
1. Initialize Tauri in the app package:
|
||||
- `pnpm add -D @tauri-apps/cli @tauri-apps/api` in packages/app
|
||||
- Configure window: frameless with custom titlebar, transparent background
|
||||
- System tray with quick-access menu
|
||||
- Global shortcut (Cmd+Shift+A) to show/hide window
|
||||
|
||||
2. Tauri config:
|
||||
- Window size: 1200x800, min 800x600
|
||||
- Transparent background (for glass morphism)
|
||||
- Auto-updater enabled
|
||||
- File system scope: app data directory only
|
||||
|
||||
**Acceptance criteria**:
|
||||
- `pnpm tauri dev` launches desktop app
|
||||
- Glass morphism renders correctly with transparent window
|
||||
- System tray works
|
||||
- `pnpm tauri build` produces .dmg/.app
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
#### Task M7.4: Offline Mode
|
||||
|
||||
*(Detailed specs to be written when M6 is complete)*
|
||||
**Why**: AIUI should work without internet for browsing saved content.
|
||||
|
||||
**Files to create/modify**:
|
||||
- Modify `packages/app/src/sw.ts` or PWA config — cache strategies
|
||||
- Create `packages/app/src/composables/useOffline.ts`
|
||||
- Modify UI components — offline indicators
|
||||
|
||||
**Implementation**:
|
||||
1. Create `useOffline.ts`:
|
||||
```ts
|
||||
export function useOffline() {
|
||||
const isOnline: Ref<boolean> // navigator.onLine + event listeners
|
||||
const pendingSync: Ref<number> // count of items waiting to sync
|
||||
function queueForSync(action: SyncAction): void
|
||||
function processSyncQueue(): Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
2. PWA cache strategies:
|
||||
- App shell: cache-first (HTML, JS, CSS, fonts)
|
||||
- API responses: network-first with cache fallback
|
||||
- Images: cache-first with stale-while-revalidate
|
||||
- IndexedDB data: always available offline
|
||||
|
||||
3. UI indicators:
|
||||
- Subtle banner when offline ("Offline — browsing saved content")
|
||||
- Disable AI chat input when offline (grey out with tooltip)
|
||||
- Show cached content (favorites, saved conversations)
|
||||
|
||||
**Acceptance criteria**:
|
||||
- App loads without internet
|
||||
- Saved conversations and favorites accessible offline
|
||||
- Chat disabled with clear offline indicator
|
||||
- Reconnection triggers sync
|
||||
- `pnpm typecheck` passes
|
||||
|
||||
---
|
||||
|
||||
@@ -559,20 +982,43 @@ For automated late-night Claude sessions, execute tasks in this order:
|
||||
|
||||
### Priority Queue (each session picks next incomplete task):
|
||||
|
||||
1. **Task 1.1** — Create PROGRESS.md *(5 min)*
|
||||
2. **Task 1.2** — Create post-push progress hook *(10 min)*
|
||||
3. **Task 1.3** — Register hook in settings.json *(2 min)*
|
||||
4. **Task M1.2** — Error boundaries *(15 min)*
|
||||
5. **Task M1.3** — Unit tests for contentExtraction *(20 min)*
|
||||
6. **Task M1.1** — IndexedDB persistent storage *(25 min)*
|
||||
7. **Task M1.4** — Unit tests for useAI *(20 min)*
|
||||
8. **Task M2.1** — Markdown rendering *(20 min)*
|
||||
9. **Task M1.5** — E2E test expansion *(15 min)*
|
||||
10. **Task M2.3** — Music source resolution *(20 min)*
|
||||
11. **Task M2.2** — Virtual scrolling *(15 min)*
|
||||
12. **Task M2.4** — Nostr feed integration *(25 min)*
|
||||
13. **Task M3.1** — Activate plugin registry *(20 min)*
|
||||
14. **Task M3.2** — Renderer plugin registration *(20 min)*
|
||||
**M1: Stability & Polish**
|
||||
1. **Task M1.2** — Error boundaries *(verify existing ErrorBoundary.vue, wrap remaining components)*
|
||||
2. **Task M1.3** — Unit tests for contentExtraction *(create __tests__/contentExtraction.test.ts)*
|
||||
3. **Task M1.1** — IndexedDB persistent storage *(create idb-storage.ts, modify chat.ts)*
|
||||
4. **Task M1.4** — Unit tests for useAI *(create __tests__/useAI.test.ts with mocked fetch)*
|
||||
5. **Task M1.5** — E2E test expansion *(8 new tests in content-surfaces.spec.ts)*
|
||||
|
||||
**M2: Content Experience**
|
||||
6. **Task M2.1** — Markdown rendering in chat *(add markdown-it, modify ChatMessage.vue)*
|
||||
7. **Task M2.3** — Music source resolution + queue *(fix usePlayer.ts, update PlayerBar.vue)*
|
||||
8. **Task M2.2** — Virtual scrolling for chat *(add @tanstack/vue-virtual to ChatWindow.vue)*
|
||||
9. **Task M2.4** — Nostr feed integration *(create useNostr.ts, update NostrGrid.vue)*
|
||||
|
||||
**M3: Plugin System**
|
||||
10. **Task M3.1** — Activate plugin registry *(create plugins/index.ts, claude-provider.ts)*
|
||||
11. **Task M3.2** — Renderer plugin registration *(film/song renderer plugins)*
|
||||
|
||||
**M4: Social & Discovery**
|
||||
12. **Task M4.1** — Social embeds *(NostrEmbed.vue, nostr: URI detection in chat)*
|
||||
13. **Task M4.2** — Federated search *(useFederatedSearch.ts, /search command)*
|
||||
14. **Task M4.3** — Bookmarks/favorites *(favorites.ts store, FavoriteButton, FavoritesGrid)*
|
||||
|
||||
**M5: Security & Privacy**
|
||||
15. **Task M5.1** — E2E encryption *(crypto.ts with Web Crypto API AES-256-GCM)*
|
||||
16. **Task M5.2** — Encrypted storage layer *(PassphraseDialog, wire encryption to all stores)*
|
||||
17. **Task M5.3** — API key vault *(key-vault.ts, ApiKeyManager.vue)*
|
||||
|
||||
**M6: Payments & Identity**
|
||||
18. **Task M6.1** — Lightning wallet deep-links *(lightning.ts, PaymentButton, LightningInvoice)*
|
||||
19. **Task M6.2** — Cashu token support *(cashu.ts, CashuToken.vue inline in chat)*
|
||||
20. **Task M6.3** — Nostr identity NIP-07 *(useNostrIdentity.ts, NostrLogin.vue)*
|
||||
|
||||
**M7: Platform**
|
||||
21. **Task M7.1** — MCP server integration *(mcp-server.ts, tool routing in useAI)*
|
||||
22. **Task M7.2** — Multi-provider AI normalization *(adapter pattern for Claude/OpenRouter/Ollama)*
|
||||
23. **Task M7.3** — Tauri desktop build *(src-tauri config, transparent window, system tray)*
|
||||
24. **Task M7.4** — Offline mode *(useOffline.ts, cache strategies, offline UI indicators)*
|
||||
|
||||
### Session Protocol
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
- [x] Unit tests — contentExtraction composable (10 extraction functions, 49 tests)
|
||||
- [x] IndexedDB persistent storage (conversations survive refresh)
|
||||
- [x] Unit tests — useAI composable (16 tests, mocked fetch/SSE)
|
||||
- [ ] E2E test expansion (8 new tests: streaming, content cards, mobile, etc.)
|
||||
- [x] E2E test expansion (8 new tests: streaming, content cards, mobile, etc.)
|
||||
|
||||
### M2: Content Experience ✅
|
||||
- [x] Markdown rendering in chat (markdown-it, XSS safe)
|
||||
|
||||
Reference in New Issue
Block a user