Commit graph

129 commits

Author SHA1 Message Date
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
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
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
Bruno Deanoz
1899f2cff1 fix: voice messages show as compact pill instead of file attachment chip
Audio-only user messages (no text, only audio attachments) now render as
a clean "🎤 Sprachnachricht" pill instead of the ugly filename chip
(voice_1782170416740.m4a). Detects via attachments.all { kind == "audio" }
&& content.isBlank(). Regular file attachments are unaffected.
2026-06-23 01:24:56 +02:00
Bruno Deanoz
fb6bc981d1 feat: web search provider picker for Vertex models (Google vs Enterprise)
When search mode is active and a vertex: model is selected, a second pill
appears next to "Suche" with a dropdown to choose between Google Search
(default) and Enterprise Web Search grounding. The selection is sent as
webSearchProvider in the chat request body. Matches the web frontend's
OptionPill for webSearchProvider.

Only visible for Vertex models — OpenRouter uses agentive web_search
server-tool which has no provider toggle.
2026-06-23 01:23:03 +02:00
Bruno Deanoz
4150936b18 fix: sampling panel too large + pills barely visible
Sampling panel: width 260→220dp, padding reduced, spacing 14→6dp between
parameters, checkbox 20→16dp, slider height capped at 28dp, removed extra
spacer between label row and slider. Panel is now ~40% shorter.

Pills visibility: inactive background alpha raised (dark 0.06→0.10, light
0.04→0.07), border alpha doubled (dark 0.08→0.14, light was white-on-light
invisible, now uses black 0.10), icon tint 0.60→0.75, text 0.70→0.80.
Active state also strengthened. Pills are now clearly visible in both modes.
2026-06-23 01:17:56 +02:00
Bruno Deanoz
d289466276 feat: live voice waveform replaces static recording indicator
32-bar waveform visualization that reacts to microphone amplitude in
real-time. MediaRecorder.getMaxAmplitude() polled every 60ms, normalized
to 0-1 range, fed into a scrolling bar chart (new bars enter right, old
shift left). Bars use accent color — louder = taller + more opaque,
quiet = short + muted. Replaces the static "Aufnahme läuft…" text.
2026-06-23 01:15:51 +02:00
Bruno Deanoz
20a71adb99 feat: voice recording auto-send + recording indicator UI
Voice messages now upload and send automatically when recording stops —
no manual send button needed. The input area transforms during recording:
text field replaced by a centered pulsing red dot + "Aufnahme läuft…" label,
with a single centered red stop button below.

The old flow (record → attachment chip → manual send) treated voice like
a file attachment. New flow: tap mic → record → tap stop → auto-upload →
auto-send → stream response. Matches the expected voice message UX.
2026-06-23 01:12:48 +02:00
Bruno Deanoz
497f0a82f7 feat: redesign mode pills and dropdowns
Pills: extracted shared ModePill composable — thinner border (0.8dp gradient
instead of 1.2dp solid), subtler inactive background, smaller text (12.5sp),
compact padding (12x7 instead of 13x8). Active state uses accent gradient
border instead of solid color.

Reasoning dropdown: styled container (dark glass bg, 16dp shadow, squircle
shape), check icon on selected preset, cleaner row layout.

Sampling popover: same styled container, custom checkbox (squircle with accent
tint instead of Material Checkbox), disabled state dims labels and track,
wider panel (260dp), more spacing between parameters.
2026-06-23 01:07:57 +02:00
Bruno Deanoz
fcac983201 fix: flat message action icons — remove background box
Clean flat icons without bordered container. 32dp touch target (circle clip
for ripple), 18dp icon size, no background fill. Modern minimal look.
2026-06-23 01:04:30 +02:00
Bruno Deanoz
dea379e237 fix: increase message spacing from 16dp to 24dp 2026-06-23 01:02:47 +02:00
Bruno Deanoz
0ade063b34 feat: improve message action buttons — larger, visible, subtle background
Buttons: 30dp→34dp with squircle shape (KaizenShapes.sm), subtle tinted
background (dark: white 4%, light: black 2.5%), icons 16→17dp. Tint alpha
raised from 0.35→0.50 (copy/regenerate/volume) and 0.6→0.70 (delete) so
buttons are actually visible in both light and dark mode. Spacing 2→4dp
between buttons, top padding 4→6dp.
2026-06-23 01:02:07 +02:00
Bruno Deanoz
10ece6d72f feat: rounder chat items with card-style borders
Items use KaizenShapes.md (16dp radius) instead of sm (12dp), more spacing
(4dp gap), horizontal padding, and subtle glass borders on inactive items.
Active items get accent gradient border + shadow. Matches web's rounded-xl
card treatment.
2026-06-23 00:59:58 +02:00
Bruno Deanoz
558b1be675 feat: sidebar redesign — glasier look, functional search, generate title, duplicate
Design: lower tintAlpha (0.80/0.86) for more blob bleed-through, accent-tinted
gradient borders + level1 shadow on active items, specular borders on search bar
and user card, accent gradient avatar, separator line in context menu.

Features: live search filter, "Titel generieren" (Sparkles, manual POST with
{ manual: true }), "Duplizieren" (copies all messages with new IDs + parentId
chain), titleGenerated field on ConversationSummary/Entity (Room v4).
2026-06-23 00:56:58 +02:00
Bruno Deanoz
1645be8a97 feat: sync TTS voice from server — app reads ttsVoice from /api/v1/me
Voice preference set in web settings now automatically applies in the app.
Falls back to "Kore" if no server preference set.
2026-06-23 00:39:34 +02:00
Bruno Deanoz
c4884a3748 feat: TTS audio cache in Room — skip regeneration on replay
- MessageEntity gets ttsUrl + ttsVoice fields (Room version 2→3)
- MessageDao.getTtsUrl() checks cache before API call
- MessageDao.updateTts() saves URL + voice after generation
- readAloud() checks Room first, only calls /api/v1/audio-gen on miss
- Voice change invalidates cache automatically (query includes voice)
- Default voice "Kore" (Gemini-TTS)
2026-06-23 00:33:09 +02:00
Bruno Deanoz
9e1f30de1f feat: voice recording + TTS read-aloud for assistant messages
Audio recording:
- Mic button wired to MediaRecorder (AAC/M4A, 44.1kHz/128kbps)
- Tap to start, tap again to stop — auto-uploads as audio attachment
- RECORD_AUDIO permission with runtime request
- Recording state: red stop-square on mic button

TTS read-aloud:
- Volume button on assistant messages → POST /api/v1/audio-gen
- Gemini-TTS generates speech server-side, MediaPlayer streams it
- Tap again to stop playback, accent-colored icon when playing
- Graceful error handling (Toast on failure)

Supporting changes:
- KaizenApi.generateSpeech() for /api/v1/audio-gen endpoint
- KaizenIcons.Volume2 added (Lucide)
- DE/EN strings for recording, permission, TTS states
2026-06-23 00:27:02 +02:00
Bruno Deanoz
edd8ccaa05 fix messages overlapping mode pills — increase bottom padding to 220dp
The two-row ChatInput redesign made the bottom dock ~210dp tall but
contentPadding was still 152dp, causing the last message to scroll
behind the mode pills.
2026-06-23 00:13:21 +02:00
Bruno Deanoz
141d4de227 fix locale, add stop button, auto-logout on 401, migrate login shadows
- EmptyHero date respects system locale instead of hardcoded German
- Stop button (red square) replaces send/call during streaming, cancels stream cleanly
- Auto-logout on HTTP 401 (expired token) routes to login instead of error banner
- LoginScreen migrated from Modifier.shadow to kaizenShadow (design system pillar 4)
2026-06-23 00:02:30 +02:00
Bruno Deanoz
bf9441422b style CallButton to match SendButton — accent gradient, press animation
Same 38dp circle as SendButton. Subtle accent gradient fill (16-28%
alpha), AudioLines icon in accent.primary color, press-scale animation.
Visually pairs with the send button as its idle counterpart.
2026-06-22 19:21:16 +02:00
Bruno Deanoz
fd0de5ccef upgrade Markdown renderer — tables, strikethrough, clickable links, task lists
New block types: Table (header + separator + body rows, horizontal scroll,
styled borders), task lists (- [ ] / - [x] with check icons).
New inline: ~~strikethrough~~, clickable links (tap opens ACTION_VIEW).
Code highlighting: block comments (/* */), HTML comments (<!-- -->),
template strings, SQL case-insensitive keywords, added C/C++/Ruby/PHP/
Dockerfile/TOML language support. All regex patterns pre-compiled as
top-level constants for streaming performance.
2026-06-22 19:20:17 +02:00
Bruno Deanoz
70aa5c56da fix ChatInput — restore mic/call toggle, softer send button
Mic (voice recording) stays left next to +. Right side toggles
between AudioLines icon (call, when empty) and send button
(when content). Send button: removed border, translucent gradient,
38dp instead of 44dp.
2026-06-22 19:16:50 +02:00
Bruno Deanoz
313c21d47d perf: reduce sensor rate and pause blob animation during chat
Sensor: SENSOR_DELAY_GAME → SENSOR_DELAY_UI (~15Hz instead of ~60Hz),
parallax effect unchanged. Blob drift animation only runs on empty
state, frozen at midpoint during chat (blobs still visible, no GPU
animation cost).
2026-06-22 19:01:25 +02:00
Bruno Deanoz
f703196087 soften bottom dock scrim — transparent gradient instead of opaque block
Keyboard closed: 0% → 25% → 55% alpha gradient.
Keyboard open: 50% → 85% instead of solid fill.
Pills and input now float over the mesh background.
2026-06-22 18:58:04 +02:00
Bruno Deanoz
34a527cdb6 redesign ChatInput — two-row layout, rounder, elevated
Text field on top with full width, action icons below.
More padding (16dp margins, 14dp inner), pill shape with
32dp corners, level3 shadow for float effect. Plus and Mic
buttons get subtle circular backgrounds.
2026-06-22 18:54:32 +02:00
Bruno Deanoz
ad11c0ee46 fix: multi-select for gallery and file picker
Gallery uses PickMultipleVisualMedia (image + video), file picker uses
OpenMultipleDocuments — both now allow selecting multiple items at once.
Camera stays single-shot (one photo per shutter, standard behavior).
Each selected item uploads in parallel as before.
2026-06-22 16:29:35 +02:00
Bruno Deanoz
68721d8ddf feat: KaizenIcons system — Lucide base, full Material Icons migration
Introduces a centralized icon registry (ui/icon/KaizenIcons.kt) that
decouples every UI component from the underlying icon library. All 10
source files migrated from Material Icons Rounded to KaizenIcons backed
by Lucide (1.5px stroke, same library as the web frontend).

Architecture:
- KaizenIcons object with semantic getters (Send, Lock, Brain, etc.)
- Grouped by function (Navigation, Chat, Modes, Files, Settings, etc.)
- Custom brand section ready for hand-drawn SVG overrides
- One-line swap: change the getter, every usage updates
- Zero Material Icon imports remaining in app source

Dependency: com.composables:icons-lucide-android:1.0.0
2026-06-22 14:11:34 +02:00
Bruno Deanoz
1362daee53 fix: snappy sidebar — optimistic insert, instant title, smart merge
Three fixes for sluggish conversation list:

1. Optimistic sidebar insert: new conversation appears in the sidebar
   immediately after creation (with first-line preview as title),
   before the stream finishes.

2. generateTitle now returns the title from the server response and
   updates Room directly via updateTitle() — no blind refresh that
   races with server-side generation.

3. replaceAll uses upsert + deleteExcept instead of deleteAll + insert,
   eliminating the empty-list flash that caused sidebar flicker.
2026-06-22 13:57:52 +02:00
Bruno Deanoz
1929073ad5 redesign top bar — unified glass pill for sidebar + model
Merge the separate hamburger circle and ModelPill into one cohesive
GlassSurface pill. Menu icon (17dp, muted) sits left, subtle 3dp dot
divider, then model name (13sp Medium, 180dp max with ellipsis).
No more chunky ExpandMore chevron. Matches the TokenBadge's glass
language on the right.
2026-06-22 13:54:08 +02:00
Bruno Deanoz
e62c525386 redesign app icon — glass-lens orb with depth
10-layer foreground: soft halo, warm amber body with 6-stop gradient,
cool glass overlay, bounce light, volume shadow, internal caustic
structure, elliptical specular + micro pinpoint glint, gradient rims.
Replaces the flat 5-layer amber ball that looked like an emoji.

Background: warmer cream (light) with vignette, deeper obsidian (dark)
with warm center glow. Monochrome: stronger contrast + rim for themed
icon legibility.
2026-06-22 13:49:43 +02:00
Bruno Deanoz
ba7ebafe08 feat: security settings — devices, password change, biometric toggle
Security section in Settings with three cards:
- Biometric toggle (persisted in SecureStore, respected by unlock flow)
- Signed-in devices list with remote revoke (GET/DELETE /api/v1/auth/tokens)
- Password change form (POST /api/v1/auth/change-password)

requestBiometricOrPassword() now checks the biometric toggle — when
disabled, always falls back to password dialog.
2026-06-22 13:44:01 +02:00
Bruno Deanoz
15f5781824 fix: close biometric bypass on locked conversations
Three paths in openConversation() called unlockAndOpen() without any
authentication when biometrics were unavailable. Sidebar ToggleLock
also sent PATCH { locked: false } without auth.

- Unified requestBiometricOrPassword() gates all unlock paths
- PasswordUnlockDialog as fallback when biometrics unavailable
- KaizenApi.requestUnlock() sends password to server for verification
- All unlock flows (open chat + sidebar toggle) now require auth
2026-06-22 13:27:55 +02:00
Bruno Deanoz
e9e2a66b7d stronger haptics + live language switching
Haptics: all feedback stronger and more distinct —
- click (send): 1.0 intensity CLICK (was 0.85)
- thinkingStart: double-tap TICK 0.5→0.7 (was single 0.22)
- responseStart: three-step crescendo TICK→CLICK→CLICK (firmer)
- responseEnd: firm CLICK + settling TICK (was soft 0.3 TICK)

Language: selectLocale() now applies immediately —
- API 33+: LocaleManager.setApplicationLocales (no restart)
- API <33: updateConfiguration + activity.recreate()
No new dependencies needed.
2026-06-22 01:31:02 +02:00
Bruno Deanoz
163a2cee28 fix 3 bugs: pills with keyboard, token count, chat model
1. Mode pills stay visible when keyboard open (were hidden).
   Spacing reduced (6dp vs 24dp) to keep dock compact.

2. Token counter works for loaded chats — sum inputTokens +
   outputTokens from all StoredMessages when fetching from
   server. StoredMessage now parses model/inputTokens/outputTokens.

3. Model pill shows the chat's actual model (from last assistant
   message) instead of always the global default. chatModel state
   tracks per-conversation model, falls back to session default
   for new chats.
2026-06-22 01:27:29 +02:00
Bruno Deanoz
c014d6d90c fix keyboard handling — offset dock instead of padding
adjustResize didn't work with enableEdgeToEdge (input hidden
behind keyboard). imePadding inflated the dock (input too high).

New approach: read WindowInsets.ime bottom value directly and
apply it as negative Y offset on the dock. The dock slides up
by exactly the keyboard height — no inflation, no layout changes.
windowSoftInputMode=adjustNothing (edge-to-edge default).

When keyboard closed: offset = nav bar height (gesture bar).
When keyboard open: offset = keyboard height (includes nav bar).
2026-06-22 01:21:32 +02:00
Bruno Deanoz
420bcd7cb6 fix input field way too high — use adjustResize instead of imePadding
imePadding() on Compose Box/Column with overlapping children
(BottomCenter-aligned dock over fillMaxSize LazyColumn) caused
the input to float to the top of the screen. The Compose inset
system doesn't work well with this overlay layout pattern.

Fix: remove all imePadding() usage, set windowSoftInputMode=
adjustResize in AndroidManifest. Android resizes the window
itself when the keyboard opens — the entire layout naturally
reflows, dock stays at the bottom, chat area shrinks. This is
the standard approach for chat apps with edge-to-edge.
2026-06-22 01:17:59 +02:00
Bruno Deanoz
e697dd7e76 fix error banner sticky, token badge always visible
- loadError auto-clears after 4s for unlock failures
- loadError reset on every conversation switch (was sticky)
- Token badge always shown (was hidden when usedTokens == 0,
  making it invisible for all loaded chats)

Note: locked chat "Authentifizierungsfehler" happens because
POST /api/v1/auth/unlock is not deployed yet on the server.
After git pull + docker build on prod, biometric unlock works.
2026-06-22 01:14:55 +02:00
Bruno Deanoz
77976ea974 move imePadding to outer Box — fix input floating into chat area
The root cause: imePadding on the bottom dock Column inflated it
by the full keyboard height, pushing it up into the chat content.

Fix: imePadding on the outer Box that contains everything. Now
the entire layout shrinks when keyboard opens — chat area gets
shorter, dock stays at the (new) bottom edge. No overlap.
2026-06-22 01:13:40 +02:00
Bruno Deanoz
27deaca2e4 fix locked chat not opening — context cast + biometric fallback
Two issues prevented locked chats from opening:

1. context as? FragmentActivity silently returned null on some
   devices where LocalContext.current is a ContextWrapper. Fix:
   walk the ContextWrapper chain to find the FragmentActivity.

2. No fallback when biometrics unavailable — the chat just stayed
   locked with no feedback. Fix: if biometrics not available (no
   fingerprint enrolled, no hardware), skip biometric prompt and
   unlock directly via server token. Biometric is still enforced
   when available.

Extracted unlockAndOpen() to avoid duplicating the server unlock
+ message loading logic across biometric/fallback paths.
2026-06-22 01:10:57 +02:00
Bruno Deanoz
ba01902b08 fix input field overlapping chat when keyboard open
Root cause: gradient scrim (transparent at top) let chat text
bleed through when imePadding pushed the dock up. Fix: use solid
background color when keyboard is visible, gradient only when
keyboard is closed. Pills and spacer also hidden during keyboard.

Input field: pill shape instead of xl (rounder), higher opacity
(0.85/0.90 instead of 0.75/0.74) for cleaner look — less muddy
glass, more solid surface. Bottom spacer reduced to 4dp when
keyboard open (was 16dp) to sit tight above the keyboard.
2026-06-22 01:09:00 +02:00