Commit graph

180 commits

Author SHA1 Message Date
Bruno Deanoz
57cc69c999 remove accidentally committed file 2026-06-24 12:35:17 +02:00
Bruno Deanoz
e5690b2caa fix Live Call: show error details instead of infinite "Verbinde..."
Error events from the WS server were silently swallowed — the onError
handler was empty and provider_error didn't trigger endCall. Now:
errors display in the overlay subtitle and loadError banner, and
provider_error is treated as terminal (ends the call).
2026-06-24 12:34:59 +02:00
Bruno Deanoz
1e5bbf38b9 fix Live Call overlay: opaque MeshBackground instead of transparent scrim
The overlay used a semi-transparent Color.White/Black scrim (alpha 0.85-0.88),
letting the chat content bleed through. Replaced with MeshBackground
(frozen blobs, no animation) for a proper opaque fullscreen call UI.
2026-06-24 12:09:17 +02:00
Bruno Deanoz
0744194c84 Expand auto-search to tech products, movies, series, comparisons
New pattern categories:
- Tech products: iPhone/Galaxy/Pixel/iPad + version numbers, specs,
  release dates, "neuestes Handy", Apple Vision Pro, Meta Quest, etc.
- Movies/series: Staffel/Season/Episode numbers, cast, ratings,
  neue Serie/neuer Film, IMDB, Rotten Tomatoes
- Product comparisons: "vs" with model numbers, "lohnt sich"
- Current leaders: Bundeskanzler, CEO, Präsident

These are high-hallucination topics where the AI's training data
is likely outdated — auto-search gives it fresh facts.

8 new tests (tech, movies, comparisons, leaders).
2026-06-24 10:34:05 +02:00
Bruno Deanoz
feb326fc40 Auto-search detection + live reasoning preview
Auto-search:
- detectSearchQuery() pattern-matches common search prompts in DE+EN
  (weather, news, prices, scores, current events, etc.)
- send() enables webSearch automatically when detected, no manual
  pill needed. Manual pill still works as explicit override.
- 30+ patterns ported from backend lib/chat/detection.ts

Live reasoning preview:
- ReasoningBlock shows first meaningful line as preview label when
  collapsed (max 80 chars, ellipsis). Updates live during streaming.
- "Gedankengang" header + preview line stacked vertically.
- Preview hidden when block is expanded (full text visible).

Tests: 4 new tests for detectSearchQuery (DE patterns, EN patterns,
normal prompts not triggered, short input ignored).
2026-06-24 10:30:52 +02:00
Bruno Deanoz
77d2d8e5df Redesign SourcesBlock — horizontal chips + detail cards
Before: sources hidden behind a collapsed "Quellen (3)" toggle,
vertical stack of flat cards, requires click to see anything.

After:
- Always-visible horizontal scrollable chips with numbered colored
  circles (indigo/violet/cyan/emerald/amber/red/pink/blue cycling)
- Each chip shows the domain name, tap opens the URL
- Search query shown above sources when available
- "Details anzeigen" expands to full cards with colored left bar,
  title, domain, and snippet
- Perplexity/Gemini-inspired compact visual style
2026-06-24 10:23:24 +02:00
Bruno Deanoz
24d6b2148a Add image generation + streaming recomposition isolation
Image generation:
- KaizenApi.generateImage() calls POST /api/v1/image-gen
- Bild mode pill in ChatInput triggers image generation instead of chat
- Generated image displayed as attachment in assistant message
- Conversation created + title generated automatically

Streaming isolation (from previous uncommitted work in ChatScreen):
- liveStream StateFlow emits content during streaming
- Only the active MessageRow subscribes via collectAsState()
- Other LazyColumn items stay completely static during streaming
2026-06-24 10:18:33 +02:00
Bruno Deanoz
f746c603b0 Isolate streaming recomposition to active bubble only
During streaming, content updates now flow through a StateFlow
(liveStream) instead of mutating the SnapshotStateList on every chunk.
Only the single MessageRow with id == streamingMessageId subscribes to
the flow via collectAsState(). All other items in the LazyColumn stay
completely static during streaming.

Before: every chunk mutated messages[idx] → entire LazyColumn evaluated
After: only the live bubble recomposes, list mutation happens once at
stream end

Auto-scroll split into two LaunchedEffects: one for list size changes,
one for liveStream emissions.
2026-06-24 10:15:24 +02:00
Bruno Deanoz
06757d6f69 Update CLAUDE.md with ChatScreenViewModel architecture 2026-06-24 10:10:43 +02:00
Bruno Deanoz
40db869717 Extract ChatScreenViewModel from God Composable
Move all business logic and state (~35 variables, ~15 functions) from
the 1540-line ChatScreen @Composable into ChatScreenViewModel.

ChatScreen is now a thin rendering layer (~430 lines) that reads VM
state and wires UI events to VM methods. Android-framework-only concerns
(ActivityResultLaunchers, DrawerState, LazyListState, keyboard insets)
stay in the Composable.

Changes:
- New ChatScreenViewModel.kt: messages, streaming, conversation mgmt,
  sidebar actions, file upload, voice recording, TTS, live call, unlock
- ChatScreen.kt: pure UI, delegates all logic to vm.*
- Haptics.kt: add createHaptics(Context) factory for non-Composable use
- SessionViewModel: store nullable for test construction
- MainActivity: instantiates ChatScreenViewModel

Tests: 12 new unit tests for ChatScreenViewModel (state transitions,
newChat reset, mode toggles, edit/delete, password dialog lifecycle).
All 90 tests pass.
2026-06-24 10:09:40 +02:00
Bruno Deanoz
89fe5fd2ae Update CLAUDE.md with security + performance audit results 2026-06-24 09:40:26 +02:00
Bruno Deanoz
3b73e15376 Security hardening + performance optimization pass
Security:
- Fix JSON injection in deleteMessage (raw string → serialized DTO)
- Separate OkHttpClient for streaming (30s timeout on normal requests,
  infinite only for chat stream)
- Disable backup (EncryptedSharedPrefs without KeyStore → crash on restore)
- Block cleartext traffic explicitly
- Strip server URLs from log output

Performance:
- Share OkHttp connection pool across KaizenApi + LiveClient
- Eliminate duplicate fetchMe/fetchDefaultModel call on login
- Parallelize prefetchMissing (was sequential per conversation)
- Stream file uploads directly from ContentResolver (no full readBytes)
- Cache assistant message index during streaming (O(1) vs O(n) per chunk)
- Build chat history before adding assistant placeholder (no filter needed)

Bugs:
- Fix send() race condition (isStreaming set before async work)
- Move voice recording file read to IO dispatcher
2026-06-24 09:38:54 +02:00
Bruno Deanoz
392e582084 fix: show toast when Live Call unavailable instead of silent no-op
The call button did nothing when no live model was available (empty
lambda). Now shows a toast explaining the issue.
2026-06-24 07:12:13 +02:00
Bruno Deanoz
da3eded81e Remove temporary bug screenshots + ignore bugs/ dir 2026-06-24 03:27:53 +02:00
Bruno Deanoz
ff66b80966 Wire Live Call end-to-end: turn persistence, model check, permission
Turn persistence: onTurnEnd handler creates conversation if needed,
adds user + assistant messages to UI list, caches in Room. Server
already persists via WS-Server — app only needs local state sync.

Model check: CallButton only triggers Live Call when a vertex:*live*
model is in the catalog (liveAvailable). Falls back to PTT otherwise.

Permission: RECORD_AUDIO runtime request via ActivityResultContracts
before starting the call. Denied → toast, no crash.

Title generation: endLiveCall triggers generateTitle + sidebar refresh,
same pattern as text chat.
2026-06-24 03:25:32 +02:00
Bruno Deanoz
31278f2440 Add Live Call UI overlay + wiring in ChatScreen
LiveCallOverlay — fullscreen composable: KaizenOrb (amplitude-reactive,
streaming/recording variants), duration timer, status text (connecting/
listening/speaking), animated subtitle (transcript/response), end call
button (red gradient circle with PhoneOff icon), interrupt on orb tap.

ChatScreen wiring: CallButton → startLiveCall() → binds LiveCallService
→ Foreground Service with AudioPipeline + LiveClient. Overlay renders
above chat content with fade animation. endLiveCall() unbinds + stops.

ChatInput: onCallClick callback propagated from CallButton to ChatScreen.

i18n: live_connecting/listening/ready/speaking/end/reconnecting in de+en.
KaizenIcons: added PhoneOff (Lucide).
2026-06-24 03:14:08 +02:00
Bruno Deanoz
65ea3a5656 Add Live Call transport layer (P10 foundation)
LiveProtocol — type-safe Kaizen WS protocol serialization/parsing,
mirrors lib/live/protocol.ts 1:1 (setup/resume/audio/text/interrupt/end
+ all 12 server event types).

LiveClient — OkHttp WebSocket client with auto-reconnect (exponential
backoff, max 5 attempts), resume protocol (sessionId + lastSeqId),
backpressure handling, terminal error detection. 15s ping interval
keeps mobile NAT alive.

AudioPipeline — bidirectional audio: AudioRecord (16kHz PCM16 mono,
VOICE_COMMUNICATION for hardware AEC/noise suppression) → 100ms chunks
→ Base64. AudioTrack (24kHz PCM16 mono, LOW_LATENCY, MODE_STREAM) ←
server chunks. Mic-ducking during KI speech (RMS threshold gating).

LiveCallService — Foreground Service (MICROPHONE type), survives
rotation/backgrounding. Notification with duration timer + end button.
Status state machine (IDLE→CONNECTING→LISTENING→SPEAKING). Orchestrates
LiveClient + AudioPipeline, emits onTurnEnd for tree integration.
2026-06-24 03:06:48 +02:00
Bruno Deanoz
25fbb17d18 fix: token badge text wrapping on long model names
Added maxLines=1 and softWrap=false to prevent "0 / 1M" from breaking
into two lines when the top bar is compressed by a long model name.
2026-06-24 03:02:33 +02:00
Bruno Deanoz
bb53b24655 fix: TTS read-aloud downloads audio via OkHttp before playing
MediaPlayer.setDataSource(url) with HTTPS URLs can silently fail due to
certificate issues, redirect handling, or listener race conditions (listeners
were set after prepareAsync). Fix: download the WAV file via OkHttp (same
client as all other network calls) to a temp file, set listeners before
prepareAsync, play from local path. Temp file cleaned up on completion/error.
2026-06-24 01:33:23 +02:00
Bruno Deanoz
287cdb4c6b feat: branch navigation for edit/regenerate instead of destructive delete
Edit creates a sibling branch (same parentId) instead of deleting messages.
Regenerate creates a new assistant branch. BranchNavigator UI shows chevron
arrows with "1/3" counter on messages with siblings. Tree state (activeRootId,
activeChild) synced to server via PATCH. New ChatTree data structure mirrors
the web frontend's tree.ts. StoredMessage.parentId parsed from API response.
2026-06-24 01:29:02 +02:00
Bruno Deanoz
7ba42338c5 docs: add locked chat privacy fixes to CLAUDE.md 2026-06-23 20:24:34 +02:00
Bruno Deanoz
d69e2bcf68 fix: block title-leaking actions on locked conversations
Locked chats could leak their real title through sidebar search,
"Generate Title", and "Duplicate" — all three now blocked.
2026-06-23 20:23:51 +02:00
Bruno Deanoz
7aefd7ba70 fix: migrate ClickableText to LinkAnnotation, add server version check
Replace deprecated ClickableText with LinkAnnotation.Url + Text (Compose
native URI handling). Add GET /api/v1/meta call after login and on each
app resume to detect API version skew — shows dismissible amber banner.
2026-06-23 14:51:20 +02:00
Bruno Deanoz
e6fa8ef8a6 docs: restore completed sections in CLAUDE.md, fix numbering
Keep [x] items as context for future sessions. Added new done items
(text selection, ChatInput capsule, visual refresh). Fixed section
numbering 1-8. Updated media cache to reflect custom disk cache.
2026-06-23 14:40:47 +02:00
Bruno Deanoz
dca9cb7593 docs: update CLAUDE.md with visual refresh + UX overhaul changes
New sections: ChatInput capsule, semantic colors, custom icons,
glass-lens app icon, text selection, flat sidebar profile.
Updated: typography (W300), blob sizes, top bar layout, icon system.
Added full bugs-fixed section for 2026-06-23 design session.
2026-06-23 14:37:53 +02:00
Bruno Deanoz
430d5b609e style: flat sidebar user row — remove heavy card background
No more card background, border, shadow on user profile. Just a flat
row with thin separator line above, avatar 34dp, subtle icon buttons.
2026-06-23 14:31:04 +02:00
Bruno Deanoz
ecb89019dc fix: move new-chat icon left of token badge in top bar 2026-06-23 14:27:24 +02:00
Bruno Deanoz
9f7e3fb8b0 fix: center text and icons vertically in input capsule
Column gets heightIn(min=64dp) + Arrangement.Center so content
sits in the middle. Placeholder maxLines=1 to prevent overflow dots.
2026-06-23 14:24:21 +02:00
Bruno Deanoz
1d47e05dbf style: taller input (64dp), rounder corners (34dp), centered text 2026-06-23 14:20:46 +02:00
Bruno Deanoz
e683419d96 style: taller capsule input matching Gemini proportions
56dp min height, 30dp corner radius, generous inner padding.
Icons 40dp touch targets, 22dp icon size. Text 16sp centered.
High opacity (0.92/0.97) for solid feel. 16dp horizontal margin.
2026-06-23 14:18:23 +02:00
Bruno Deanoz
fb8f21dfd8 style: compact capsule input like Gemini
Tighter row: 36dp icons, 6dp padding, 15sp text, capsule shape.
Higher opacity (0.90/0.96) for solid feel against background.
All action buttons (Send/Call/Stop) matched to 36dp.
2026-06-23 14:02:37 +02:00
Bruno Deanoz
e56bef05f6 style: rounder input card, stronger shadow, higher opacity
pill shape (32dp radius), level4 shadow for stronger separation from
background, tintAlpha 0.88/0.94 (more opaque, cleaner), tighter
horizontal margin (12dp).
2026-06-23 13:59:12 +02:00
Bruno Deanoz
4958ca8429 feat: single-row input, pills inside card, new-chat button
ChatInput redesigned: single row (Plus | Text | Tune | Mic | Send).
Pills hidden by default, expandable via tune icon with slide animation
inside the same GlassSurface card. Shape lg (28dp) instead of pill.

ModePillsRow moved from separate floating row into ChatInput as
pillsContent lambda. Bottom dock scrim simplified.

New Chat button (PenLine icon) added to top bar next to TokenBadge.

LazyColumn bottom padding 260→140dp (dock is now smaller).
2026-06-23 13:57:28 +02:00
Bruno Deanoz
ad98b9f499 feat: redesign app icon + splash to match glass-lens orb
Foreground: opaque plastic orb → translucent glass-lens with subtle
amber tint, lighter speculars, thinner rims. 10 layers → 8 layers.

Backgrounds: warm cream → neutral off-white (light), cleaner obsidian
(dark). Matches app's actual background colors.

Monochrome: cleaner gradient, thinner rim.

Splash screen uses the same foreground via windowSplashScreenAnimatedIcon.
2026-06-23 13:48:25 +02:00
Bruno Deanoz
1c9491e3dc feat: text selection, modern sidebar profile, markdown spacing
Text selection: SelectionContainer wraps both user and assistant
messages — long-press to select, copy, share.

Sidebar profile: avatar 30→38dp with radial gradient, rounded lg
shape, icon buttons with subtle backgrounds, bigger text (15sp W400).

Markdown: heading spacing 18→24/20dp, list items better aligned
(8dp gap, bullet muted), blockquote bar 4dp inset + 12dp gap.
2026-06-23 13:45:23 +02:00
Bruno Deanoz
95672d3124 fix: mode pills more visible, action buttons larger, revert Brain icon
Pills: higher background alpha, stronger borders, removed muted text
opacity, bigger padding (14x8dp), icon 16dp, text 13sp W400/W500.

Action buttons: Plus/Mic/Call/Send/Stop 38dp→42dp, icons 19→22dp.

Brain icon reverted to Lucide — custom version too complex at 16dp.
2026-06-23 13:11:47 +02:00
Bruno Deanoz
3c6a8911c4 feat: custom 1.0px icons for highest-visibility touchpoints
Hand-drawn Menu, Plus, Mic, AudioLines, Brain, SlidersHorizontal,
Globe, Image with 1.0px stroke (vs Lucide's 1.5px) to match the
lighter W300 typography. Registered in KaizenIcons — all usages
update automatically, no call-site changes.
2026-06-23 13:05:50 +02:00
Bruno Deanoz
0f6e55d77a feat: upgrade NetworkImageCache — disk cache, shared client, crossfade
Shared OkHttpClient via KaizenApi.imageClient (reuses connection pool),
file-based disk cache in cacheDir/img (survives app restart), 32MB
size-based memory limit instead of 50-entry count, Crossfade animation
on image load.
2026-06-23 13:00:49 +02:00
Bruno Deanoz
38678a2add refactor: extract hardcoded colors into KaizenSemanticColors
Single source of truth for error, success, indigo, link, and dropdown
colors. Replaces ~25 scattered Color(0xFF...) literals across 10 files.
Syntax highlighting and glass-surface tints intentionally left local.
2026-06-23 12:56:09 +02:00
Bruno Deanoz
75ca8c0e7b feat: visual refresh — lighter typography, message animations, richer themes
Typography: body text W400→W300 for airier feel, headings scaled up
(H1 26sp, H2 22sp, H3 18sp), W600/W500 weight hierarchy for contrast.

Animations: animateItem() on LazyColumn messages — 350ms fade-in,
spring placement, 200ms fade-out.

Themes: blobs 20% larger + better full-screen distribution, higher
alpha on OLED (0.48 peak), light mode factor 0.65→0.78, reduced
vignette, distinct primaryDeep per theme for richer gradients.
2026-06-23 12:22:38 +02:00
Bruno Deanoz
fbead82ada fix: increase chat content padding to prevent overlap with top/bottom bars 2026-06-23 07:17:12 +02:00
Bruno Deanoz
10154d98bb fix: set gradlew execute permission 2026-06-23 07:15:17 +02:00
Bruno Deanoz
ecf096bf3b docs: passkey registration plan (WebAuthn/FIDO2 via Credential Manager)
Three-phase plan: registration + management (Phase 1), authentication
for login + unlock (Phase 2), YubiKey + cross-device (Phase 3).

Covers backend changes (assetlinks.json, multi-origin verification,
v1 re-exports with Bearer auth) and app implementation (Credential
Manager API, PasskeyManager, Settings UI, strings).
2026-06-23 01:53:04 +02:00
Bruno Deanoz
fd684fff2e fix: scroll button direction follows scroll position
Single button instead of two mutually exclusive ones. Arrow direction
is based on proximity to bottom: near bottom → arrow up (scroll to top),
not near bottom → arrow down (scroll to end). Updates instantly as the
user scrolls. Hidden when already at the target end.

Old logic had an if/else that always showed ArrowUp first (because
!atTop was always true when scrolled down), making ArrowDown unreachable
in most scroll positions.
2026-06-23 01:44:47 +02:00
Bruno Deanoz
f855a26b31 fix: sidebar overlapping status bar on notch devices
statusBarsPadding() was on the inner Column (only pushed content down),
but the GlassSurface itself started at a fixed 12dp from the top edge —
on devices with tall notches (S25 Ultra) the sidebar header sat behind
the system clock/icons. Moved statusBarsPadding() to the GlassSurface
modifier so the entire glass panel starts below the status bar.
2026-06-23 01:41:07 +02:00
Bruno Deanoz
8099f463b8 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).
2026-06-23 01:39:44 +02:00
Bruno Deanoz
421b29ef5a feat: redesign ReasoningBlock + SourceCards to match design system
ReasoningBlock: accent-tinted background + gradient border (matches pill
language), KaizenShapes.md (16dp radius), larger padding, better line
height (20sp), softer icon tint. Replaces flat gray box with solid border.

SourceCards: gradient border instead of solid, KaizenShapes.md, shows
domain instead of full URL in the link line, slightly larger padding.

Both blocks now use the same visual language as the rest of the app —
gradient borders, accent tinting, squircle shapes.
2026-06-23 01:35:45 +02:00
Bruno Deanoz
b2df7e9330 feat: modern message action icons — larger, distinctive
Icons swapped to more distinctive Lucide variants:
- Copy → ClipboardCopy (clipboard with arrow, instantly recognizable)
- Edit → SquarePen (filled square with pen, modern)
- Regenerate → RotateCcw (single-arrow rotate, cleaner than RefreshCw)
- Delete/Volume stay (already good)

Size: 36dp touch target, 20dp icons (was 32/18). Tint alpha 0.50→0.55
for better visibility. Added ClipboardCopy, SquarePen, RotateCcw, PenLine
to KaizenIcons registry.
2026-06-23 01:33:59 +02:00
Bruno Deanoz
8f72afd714 feat: edit and resend user messages
Pencil icon on user messages (next to copy, before delete). Tap removes
the edited message and all messages after it from the UI + server, then
puts the text back into the input field for editing and resending.

Only shown on user messages with text content (not voice-only), and only
when not currently streaming.
2026-06-23 01:30:52 +02:00
Bruno Deanoz
44ff82a66d fix: voice messages appear in wrong order after reopening chat
Voice auto-send was saving messages to the server but not caching them
in Room with correct sortOrder. When navigating away and back, the app
loaded from Room cache (empty for voice messages) then from server,
where the tree-based ordering could differ from insertion order.

Now caches both user and assistant messages in Room with proper sortOrder
after the server save succeeds, matching the normal send() flow.
2026-06-23 01:28:53 +02:00