From 8099f463b8b03a5772b190ad31a21878c6844daf Mon Sep 17 00:00:00 2001 From: Bruno Deanoz Date: Tue, 23 Jun 2026 01:39:44 +0200 Subject: [PATCH] docs: comprehensive CLAUDE.md update for 2026-06-23 design session Added 12 new commit entries (sidebar redesign through ReasoningBlock redesign), new bugs-fixed section, updated API contract (webSearchProvider), Room schema (titleGenerated, v4), voice recording docs (auto-send + waveform), new feature checkboxes (edit/resend, sidebar search, generate title, duplicate, web search provider picker). --- CLAUDE.md | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 13c5cce..8fc1ec5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,7 +22,7 @@ Make the **native Android app** (`kaizen-app`) a valid daily-driver that talks t - **Auth:** every request carries `Authorization: Bearer kzn_...`. Backend resolves via `requireActiveUserOrToken` (`lib/auth/guard.ts`). - **Token acquisition:** in-app login (`POST {baseUrl}/api/v1/auth/token` with `{ email, password, name }`). Consumer "Sign in" pattern — no copy-paste. -- **Chat:** `POST {baseUrl}/api/v1/chat` — `{ model, messages: [{ role, content }], reasoningEffort?, preferThroughput? }`. Response is `text/plain` chunked stream (NOT SSE). Parsed by `StreamConsumer.Incremental` in `net/StreamConsumer.kt`. +- **Chat:** `POST {baseUrl}/api/v1/chat` — `{ model, messages: [{ role, content }], reasoningEffort?, preferThroughput?, webSearch?, webSearchProvider?: "google"|"enterprise", temperature?, topP?, topK? }`. Response is `text/plain` chunked stream (NOT SSE). Parsed by `StreamConsumer.Incremental` in `net/StreamConsumer.kt`. - **Sentinels** (`lib/chat/constants.ts`): `\x00` USAGE, `\x01` REASONING, `\x02` SOURCES, `\x03` TOOL, `\x04` QUERY. App currently strips all sentinels and renders only visible content. - **Models:** `GET {baseUrl}/api/v1/models` → `{ models: [{ id, name?, provider? }] }`. - **Conversations:** `GET /api/v1/conversations` → `{ conversations, hasMore }`. `POST /api/v1/conversations` → create. `GET /api/v1/conversations/[id]` → `{ messages }`. `POST /api/v1/conversations/[id]/messages` → persist. `POST /api/v1/conversations/[id]/title` → auto-title. @@ -159,7 +159,7 @@ Architecture decision document: `OFFLINE_CACHE_ADD.md`. **Stack:** Room 2.8.4 + KSP 2.2.10-2.0.2. `room-ktx` merged into `room-runtime` since Room 2.7 (no separate dep needed). `exportSchema = false`, `fallbackToDestructiveMigration()` — it's a cache, not a DB. On schema changes the SQLite is wiped; cache rebuilds on next server fetch. **Schema:** -- `conversations` — id, title, locked, pinned, updatedAt, cachedAt +- `conversations` — id, title, locked, pinned, updatedAt, cachedAt, titleGenerated - `messages` — id, conversationId (FK CASCADE), role, content, attachments (JSON via TypeConverter), sortOrder **Data flow:** @@ -268,6 +268,17 @@ All committed on `main`. Compile-verified on Mac (`./gradlew :app:compileDebugKo | `9e1f30d` | **Voice recording + TTS read-aloud** — Mic button wired to `MediaRecorder` (AAC/M4A), tap-to-record, auto-upload as audio attachment. Volume button on assistant messages → `POST /api/v1/audio-gen` → Gemini-TTS → `MediaPlayer` playback. `RECORD_AUDIO` permission | | `c4884a3` | **TTS audio cache in Room** — `ttsUrl` + `ttsVoice` on `MessageEntity` (Room v3), skip API call on replay with same voice. Voice change invalidates cache | | `1645be8` | **Server-synced TTS voice** — reads `ttsVoice` from `GET /api/v1/me`, uses it for all TTS calls. Voice set in web settings applies in app automatically | +| `558b1be` | **Sidebar redesign** — glasier look (tintAlpha 0.80/0.86), functional search (live title filter), "Titel generieren" (Sparkles, `POST .../title` with `{ manual: true }`), "Duplizieren" (copies messages with new IDs + parentId chain), `titleGenerated` on ConversationSummary/Entity (Room v4), accent gradient borders on active items + user card | +| `10ece6d` | **Rounder chat items** — KaizenShapes.md (16dp) instead of sm (12dp), 4dp gap, card-style glass borders on inactive items, horizontal padding | +| `497f0a8` | **Mode pills redesign** — extracted shared `ModePill` composable, thinner gradient borders (0.8dp), compact padding (12×7dp), styled reasoning dropdown (check icon on selected), compact sampling panel (220dp width, 28dp slider height, 16dp custom checkbox) | +| `20a71ad` | **Voice recording auto-send** — tap mic → record → tap stop → auto-upload → auto-send → stream response (no manual send). Recording UI: live 32-bar waveform visualization (amplitude from `MediaRecorder.getMaxAmplitude()` polled every 60ms), centered red stop button | +| `d289466` | **Live voice waveform** — scrolling bar chart reacts to microphone amplitude in real-time, bars use accent color, louder = taller + more opaque | +| `fb6bc98` | **Web search provider picker** — for Vertex models: Google vs Enterprise Web Search dropdown pill, `webSearchProvider` sent in chat request | +| `1899f2c` | **Voice message display** — audio-only user messages render as compact "🎤 Sprachnachricht" pill instead of ugly filename chip | +| `44ff82a` | **Voice message ordering fix** — caches user + assistant messages in Room with correct `sortOrder` after server save (was missing, caused wrong order on reload) | +| `8f72afd` | **Edit and resend user messages** — SquarePen icon on user messages, removes message + all after it, puts text back in input field | +| `b2df7e9` | **Modern message action icons** — ClipboardCopy (copy), SquarePen (edit), RotateCcw (regenerate), 36dp touch target, 20dp icons | +| `421b29e` | **ReasoningBlock + SourceCards redesign** — accent-tinted background + gradient borders, KaizenShapes.md (16dp radius), domain instead of full URL in source cards | **Earlier UI/feel work (Phase 0 prototype → feel-first):** - Liquid glass styling, floating panels, glassmorphic sidebar @@ -313,6 +324,19 @@ All committed on `main`. Compile-verified on Mac (`./gradlew :app:compileDebugKo - **LoginScreen used `Modifier.shadow`** — violated design system pillar 4 (multi-layer shadows). Fix: migrated to `kaizenShadow` (level1 for fields, level2 for button). - **Migration 0030 crash** — `pnpm db:generate` bundled the entire org schema (0026-0029) + `tts_voice` into one migration, causing duplicate CREATE TABLE / ADD COLUMN failures. Fix: reduced 0030 to only `ALTER TABLE users ADD COLUMN IF NOT EXISTS tts_voice text`. +### Bugs fixed + features (2026-06-23 — design + sidebar + voice session) + +- **Sidebar too opaque / bland** — tintAlpha was 0.88/0.92 (barely any blob bleed-through). Fix: lowered to 0.80/0.86, added gradient borders on active items + user card + search bar. +- **Sidebar search was placeholder-only** — just showed "Suchen…" text. Fix: functional `BasicTextField` with live case-insensitive title filter + X clear button. +- **Missing sidebar features** — no "Titel generieren" or "Duplizieren". Fix: added `SidebarAction.GenerateTitle` (calls `POST .../title` with `{ manual: true }`, only shown when `titleGenerated == false`) and `SidebarAction.Duplicate` (fetches messages, creates new conversation with copied messages + parentId chain). +- **Pills barely visible in dark + light mode** — inactive background alpha was too low (0.06/0.04), border was invisible (white-on-light). Fix: raised all alpha values, borders now use visible colors in both modes. +- **Sampling panel too large** — overlay covered most of the chat. Fix: width 260→220dp, slider height capped at 28dp, spacing reduced, checkbox 20→16dp. +- **Voice messages shown as ugly file chip** — `voice_1782170416740.m4a` filename displayed. Fix: audio-only user messages render as "🎤 Sprachnachricht" pill. +- **Voice messages wrong order after reload** — auto-send code saved to server but not to Room cache. Fix: added Room caching with correct `sortOrder`. +- **Message spacing too tight** — 16dp between messages. Fix: increased to 24dp. +- **No edit/resend for user messages** — only copy + delete were available. Fix: SquarePen button removes message + successors, puts text back in input. +- **`webSearchProvider` not sent by app** — Vertex's Google vs Enterprise search toggle was missing. Fix: added `webSearchProvider` to `ChatRequest`, dropdown pill in mode pills row (only visible for `vertex:` models with search active). + ## What's NOT done yet (app, priority order) ### 1. Remaining visual/UX gaps (daily-drive blockers) @@ -347,8 +371,12 @@ These are what separate "prototype" from "daily-driver": - [x] **Attachments** — image/file upload (multi-select for gallery + files), attachment preview in messages, display in loaded conversations, camera capture, attachment picker menu (camera/gallery/file) - [x] **Reasoning/thinking display** — `ReasoningBlock` (collapsible), parses `\x01` sentinel -- [x] **Web search sources** — `SourcesBlock` (collapsible, clickable links via ACTION_VIEW), parses `\x02`/`\x04` sentinels. `webSearch: true` in ChatRequest triggers agentive search +- [x] **Web search sources** — `SourcesBlock` (collapsible, clickable links via ACTION_VIEW), parses `\x02`/`\x04` sentinels. `webSearch: true` in ChatRequest triggers agentive search. Vertex: Google vs Enterprise Web Search picker (`webSearchProvider` in request body, dropdown pill only for `vertex:` models) - [x] **Tool call display** — `ToolEventsBlock`, parses `\x03` sentinel, status icons per step +- [x] **Edit + resend user messages** — SquarePen button removes message + all successors from UI + server, puts text back in input field for editing +- [x] **Sidebar search** — live case-insensitive title filter with clear button +- [x] **Generate title from sidebar** — Sparkles icon in 3-dot menu (only when `titleGenerated == false`), calls `POST .../title` with `{ manual: true }` +- [x] **Duplicate conversation** — Copy icon in sidebar 3-dot menu, creates new conversation with all messages copied (new IDs + parentId chain) - [ ] **Native passkey login** (WebAuthn/FIDO2 on Android) - [ ] **Usage/cost display** - [ ] **User context / KI-Profil** @@ -356,7 +384,7 @@ These are what separate "prototype" from "daily-driver": ### 5. Voice & Call Mode **v1 — Pipeline (Weg A, uses existing backend):** -- [x] **Voice recording + send** — Android `MediaRecorder` (AAC/M4A, 44.1kHz/128kbps), tap-to-record mic button (red stop-square while recording), auto-upload via `/api/v1/upload`, sent as audio attachment via `/api/v1/chat`. Vertex understands audio natively; OpenRouter passes it as `input_audio`. Persisted in DB as normal attachment +- [x] **Voice recording + auto-send** — Android `MediaRecorder` (AAC/M4A, 44.1kHz/128kbps), tap mic → live 32-bar waveform visualization (amplitude from `getMaxAmplitude()` polled 60ms, scrolling bars in accent color) → tap stop → auto-upload + auto-send (no manual send needed). Voice-only user messages render as compact "🎤 Sprachnachricht" pill. Room caching with correct `sortOrder` after server save - [x] **TTS read-aloud** — Volume button on every assistant message. `POST /api/v1/audio-gen { prompt, kind: "speech", voice }` → Gemini-TTS → `MediaPlayer` streams the audio URL. Tap again to stop. Accent-colored icon during playback - [x] **TTS cache** — `ttsUrl` + `ttsVoice` fields on `MessageEntity` (Room v3). First play generates + caches; subsequent plays with same voice skip the API call. Voice change auto-invalidates - [x] **Server-synced voice** — `tts_voice` column on `users` table (migration 0030). Read from `GET /api/v1/me`, written via `PATCH /api/v1/me`. Web's `setCallVoice()` syncs to server. App reads on login/resume. Default "Kore"