From 2e323f360996cedb7c107b7a7d80f5c8cab44474 Mon Sep 17 00:00:00 2001 From: Bruno Deanoz Date: Sun, 21 Jun 2026 18:48:52 +0200 Subject: [PATCH] docs: update CLAUDE.md with all session changes Added: gradient scrims, mode pills multi-select, EmptyHero centering, settings layout fix, theme switching wiring, i18n cleanup, ToolEventsBlock state machine, attachment picker menu, background prefetch, migration watermark fix documentation. --- CLAUDE.md | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8d10647..b947553 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,6 +78,10 @@ Full spec: `DESIGN.md`. **Migration status:** All UI components migrated to the design system. Sidebar, ChatInput, ChatScreen, LoginScreen, ModelSheet, SettingsCard, SettingsScreen use `GlassSurface` + `KaizenShapes` + `kaizenShadow`. Only Markdown code-block shapes remain as `RoundedCornerShape` (intentionally rectangular for code). +**Theme switching fully wired:** All 7 files that previously imported hardcoded `Amber`/`AmberDeep`/`OnAmber`/`BlobX` now read from `LocalKaizenAccent.current`. Changing theme in Settings immediately updates: message bubbles, send button, orb, mesh background blobs, sidebar highlights, model sheet, stream blocks, login screen, settings toggles. No hardcoded amber imports remain outside `Color.kt` and theme definitions. + +**Floating bar scrims:** Top bar (hamburger + model pill) and bottom dock (mode pills + input) have vertical gradient scrims (`MaterialTheme.colorScheme.background` → transparent) to prevent message content from bleeding through. Model pill opacity increased to 0.92. + **KaizenOrb** (`chat/Background.kt`) uses `rememberTiltState()` from `ui/sensor/` instead of inline sensor code. Breath animation uses `Durations.ORB_BREATH` / `ORB_BREATH_STREAMING` from `ui/motion/`. **Typography** — Inter Variable (`res/font/inter_variable.ttf`, 859 KB, SIL OFL). Full type scale from DESIGN.md §8: `displayLarge` (32sp/W600) through `labelSmall` (11sp/W500). All 9 weight axes registered (W100–W900). Negative tracking at display sizes, positive at label sizes. `@OptIn(ExperimentalTextApi::class)` for `FontVariation.Settings`. @@ -112,7 +116,7 @@ The `/api/v1/chat` stream embeds control sections via sentinel code points (from | `\x00` USAGE | trailing | Usage/cost JSON | Stripped (display TODO) | | `\x01` REASONING | toggle | Model's chain-of-thought | `ReasoningBlock` — collapsible "Gedankengang" | | `\x02` SOURCES | trailing | Web search sources JSON | `SourcesBlock` — compact cards with domain | -| `\x03` TOOL | leading | Tool-call events (JSON+newline) | `ToolEventsBlock` — status icons per step | +| `\x03` TOOL | leading | Tool-call events (JSON+newline) | `ToolEventsBlock` — state machine (thinking/analyzing/start/end/error) | | `\x04` QUERY | trailing | Search query string | Shown above sources | **StreamConsumer.Incremental** (`net/StreamConsumer.kt`) returns `StreamState` (not `String`): @@ -126,6 +130,14 @@ The `/api/v1/chat` stream embeds control sections via sentinel code points (from **UI files:** `chat/StreamBlocks.kt` — `ReasoningBlock`, `ToolEventsBlock`, `SourcesBlock`, `SourceChip`. +**ToolEventsBlock state machine** (matches web frontend `LiveToolSteps`): +- `type: "thinking"` → "✨ Verarbeitet …" (only shown when no other tool activity) +- `type: "analyzing"` → "📄 Analysiert {name} …" (file processing) +- `type: "start"` → wrench icon + tool name (in progress) +- `type: "end"` → ✅ checkmark + tool name + elapsed ms (completed) +- `type: "error"` → ❌ error icon + tool name (failed) +- Empty/thinking-only events are hidden (no generic "Tool" fallback). + ### App icon Adaptive icon: amber orb with multi-layer radial gradients (body, depth shadow, specular highlight, warm bounce light, crisp rim). Light mode: warm off-white background. Dark mode: Obsidian gradient. Monochrome layer for Android 13+ themed icons. Splash screen uses orb foreground (API 31+). Vector-only — no raster WebPs. @@ -146,19 +158,22 @@ Architecture decision document: `OFFLINE_CACHE_ADD.md`. ``` App start → Room (instant) → UI shows cached data → Background: server fetch → Room update → Flow emits → UI updates + → Background: prefetch messages for all uncached conversations Chat open → Room (instant) → Background: server fetch → Room update Send message → in-memory during streaming → Room flush after stream ends ``` +**Background prefetch:** After conversation list sync, `MessageRepository.prefetchMissing()` iterates all unlocked conversations and fetches messages for any not yet cached. This ensures all chats are readable offline, not just previously opened ones. Already-cached conversations are skipped. + **Streaming hybrid model:** During active streaming, messages are mutated in-memory (60x/s). Room is NOT written during streaming — that would be absurd I/O. After stream completion, user + assistant messages are upserted into Room. This avoids SQLite lock contention while keeping the cache fresh. **Files:** - `db/Entities.kt` — `ConversationEntity`, `MessageEntity` -- `db/ConversationDao.kt` — `observeAll()` (Flow), `replaceAll()` (transaction: deleteAll + upsertAll, avoids SQLite 999-variable limit), `lastCachedAt()` -- `db/MessageDao.kt` — `observeByConversation()`, `getByConversation()`, `upsertAll()`, `deleteByConversation()` +- `db/ConversationDao.kt` — `observeAll()` (Flow), `replaceAll()` (transaction: deleteAll + upsertAll, avoids SQLite 999-variable limit), `lastCachedAt()`, `allUnlockedIds()` +- `db/MessageDao.kt` — `observeByConversation()`, `getByConversation()`, `upsertAll()`, `deleteByConversation()`, `cachedConversationIds()` - `db/KaizenDatabase.kt` — singleton via `companion object` (double-checked locking), thread-safe -- `db/ConversationRepository.kt` — `observeAll()` (Flow of ConversationSummary), `refresh()` (server → Room) -- `db/MessageRepository.kt` — `observeMessages()`, `getCached()`, `fetchAndCache()`, `cacheMessages()` +- `db/ConversationRepository.kt` — `observeAll()` (Flow of ConversationSummary), `refresh()` (server → Room), `allUnlockedIds()` +- `db/MessageRepository.kt` — `observeMessages()`, `getCached()`, `fetchAndCache()`, `cacheMessages()`, `prefetchMissing()` (background prefetch for offline) - `db/Converters.kt` — `List` ↔ JSON string via kotlinx.serialization - `db/Mappers.kt` — `ConversationSummary` ↔ `ConversationEntity`, `StoredMessage` ↔ `MessageEntity` @@ -197,6 +212,16 @@ All committed on `main`. Compile-verified on Mac (`./gradlew :app:compileDebugKo | `4616ef2` | **Markdown rendering with syntax-highlighted code blocks** — native Compose renderer: headings, bold/italic, inline code, fenced code blocks with keyword highlighting (15+ languages), ordered/unordered lists, streaming cursor integration | | `d8dcb25` | **Attachment support — display, upload, and send files** — parse + render message attachments from loaded conversations (images inline via OkHttp + in-memory cache, other kinds as icon chips); + button wired to Android file picker with upload to `/api/v1/upload`, progress chips, send attachments with chat requests + persist in saved messages. Also: Markdown horizontal rules, blockquotes, links, code block copy button | | `9759d47` | **Offline-first cache layer (Room)** — sidebar + messages load instantly from SQLite cache, background refresh syncs with server, streaming hybrid model (in-memory during stream, Room flush after), ChatViewModel, repositories, mappers, TypeConverters, unit tests | +| `5804b33` | **Gradient scrims on floating top/bottom bars** — content no longer bleeds through the model pill or mode pills | +| `78f03ad` | **Mode pills cleanup** — removed muddy amber tint, solid surface fills, neutral borders | +| `3560665` | **Mode pills multi-select** — modes are now independent toggles, not mutually exclusive | +| `e4c6e7f` | **EmptyHero centered** — orb + greeting vertically centered between top/bottom bars | +| `fe5e93c` | **Settings layout fix** — option rows stack title above controls instead of cramming side-by-side | +| `9186594` | **Background prefetch** — all conversation messages prefetched for offline access on app start | +| `d468ec8` | **Theme switching wired** — all 7 files migrated from hardcoded Amber to `LocalKaizenAccent.current` | +| `cad445f` | **i18n cleanup** — replaced all remaining hardcoded German strings with `stringResource()` | +| `5c54b9b` | **ToolEventsBlock rewrite** — state machine matching web frontend (thinking/analyzing/start/end/error) | +| `0948931` | **Attachment picker menu** — camera capture, gallery (image/*), and file picker (*/*) via dropdown | **Earlier UI/feel work (Phase 0 prototype → feel-first):** - Liquid glass styling, floating panels, glassmorphic sidebar @@ -207,9 +232,11 @@ All committed on `main`. Compile-verified on Mac (`./gradlew :app:compileDebugKo - Dynamic keyboard handling (suggestion pills hide, spacer shrinks) - Settings screen with SettingsViewModel -### Bug fixed this session (2026-06-19/20) +### Bugs fixed (2026-06-19/20/21) -**"Verbindung zum Server fehlgeschlagen" on S25** — root cause was **server running an old build** without the v1/conversations route (404) and without Bearer auth on models (401). Fix was `git pull && docker compose -f docker-compose.prod.yml up -d --build` on the server. Additionally, the error diagnostics were improved (see `423abc0`) so future issues show the exact cause in the UI banner instead of a generic message. +- **"Verbindung zum Server fehlgeschlagen" on S25** (06-19) — server running old build without v1 routes. Fix: `git pull && docker compose up -d --build`. +- **Server 500 on conversation load** (06-21) — Drizzle migration 0025 (`model_id` column) skipped due to rogue watermark entry (`created_at = 1781600000000`) in `drizzle.__drizzle_migrations` table, caused by hand-typed fake timestamps. Fix: lowered rogue entry's timestamp, re-ran `node scripts/migrate.mjs`. +- **Deploy gotcha (migrations):** fake timestamps in journal entries 0017–0025 (60-second increments) can cause watermark conflicts. Future migrations via `pnpm db:generate` use real timestamps and won't hit this. ## What's NOT done yet (app, priority order) @@ -238,7 +265,7 @@ These are what separate "prototype" from "daily-driver": ### 4. Features for parity with the web -- [x] **Attachments** — image/file upload, attachment preview in messages, display in loaded conversations (camera capture still TODO) +- [x] **Attachments** — image/file upload, 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` + `SourceChip`, parses `\x02`/`\x04` sentinels - [x] **Tool call display** — `ToolEventsBlock`, parses `\x03` sentinel, status icons per step