Commit graph

158 commits

Author SHA1 Message Date
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
Bruno Deanoz
fb269731b7 fix scroll buttons + restyle sidebar context menu
Scroll buttons: only one shown at a time based on direction
(up when scrolled down, down when scrolled up), hidden during
streaming. Mutually exclusive instead of both visible.

Sidebar menu: replaced default Material3 DropdownMenu/MenuItem
with custom styled menu — dark surface color (1A2030 dark,
F8F9FB light), KaizenShapes.md rounded corners, 12dp shadow
elevation, custom MenuRow composable with proper spacing.
2026-06-22 01:05:35 +02:00
Bruno Deanoz
f62cc4b7ac fix token badge, hide pills on keyboard, reset tokens on nav
Three fixes:
1. Token badge only shown when usedTokens > 0 (not in old chats
   where we have no streaming data). Pushed to far right with
   Spacer weight. Reset to 0 on new chat and conversation switch.
2. Mode pills hidden when keyboard is open (WindowInsets.isImeVisible)
   — prevents the bottom dock from pushing too far up and overlapping
   chat content. Input field stays compact at the bottom.
3. ModelPill no longer constrained with weight(fill=false) which
   was causing layout issues.
2026-06-22 01:01:53 +02:00
Bruno Deanoz
e87220afb5 redesign SourcesBlock: collapsed by default, vertical cards, clickable
Sources are now collapsed by default with a "Quellen (N)" header
that expands/collapses on tap (matching web's SourcesBlock behavior).

Each source renders as a full-width vertical card (not a small
140dp chip) with title, URL in accent color, and optional snippet.
Tapping a source opens it in the browser via ACTION_VIEW intent.
2026-06-22 00:56:44 +02:00
Bruno Deanoz
6a8a76d9f8 fix crash: bump Room version to 2, enable destructive migration
Room crashed on launch with schema hash mismatch because
MessageEntity gained reasoning+sources fields but the DB version
stayed at 1. fallbackToDestructiveMigration(false) only covered
downgrades, not same-version schema changes.

Fix: version 1→2, fallbackToDestructiveMigration(true) so any
future schema change auto-wipes the cache (it's a read-cache,
server is source of truth, rebuilds on next fetch).
2026-06-22 00:53:30 +02:00
Bruno Deanoz
f22838e99a message action buttons: copy, delete, regenerate
Under each message (when not streaming), small action icons:

User messages: Copy (clipboard → green check) + Delete (red trash).
Delete calls DELETE /api/v1/conversations/[id]/messages and removes
from the in-memory list optimistically.

Assistant messages: Copy + Regenerate (refresh icon, resends the
preceding user message) + Delete. Regenerate removes the assistant
message and re-calls send() with the original user text.

30dp circular tap targets, 16dp icons, subtle 0.35 alpha — visible
but not distracting, matching the web's action button style.
2026-06-22 00:45:18 +02:00
Bruno Deanoz
4ebe812c42 fix themes barely visible: add blob3, increase alpha, soften vignette
Three issues causing themes to look nearly identical:

1. blob3 was never rendered — each theme defines 4 blob colors but
   MeshBackground only used blob1/blob2/blob4. Added blob3 (honey
   for Amber, indigo for Blau, etc.) as a 4th blob.

2. Blob alpha too low — dark 0.22-0.30, light ×0.55 made blobs
   almost invisible. Increased to 0.30-0.42 dark, ×0.65 light.
   Now Amber clearly shows warm golds/corals, Blau shows cool
   sky/cyan/indigo — themes are visually distinct.

3. Vignette too aggressive — 0.55 alpha covered blob edges.
   Reduced to 0.40 so theme colors reach further into corners.
2026-06-22 00:38:07 +02:00
Bruno Deanoz
f7849025d7 rewrite orb from opaque painted ball to transparent glass lens
The web orb looks like glass because backdrop-filter refracts the
background through it. Android has no backdrop-filter, so the old
approach painted an opaque amber ball with harsh colors (#FFF1D6,
#8D4F00 at 85% alpha) that looked like a sticker, not glass.

New approach: very low alpha accent tint (15-22%) so background
blobs bleed through, creating a lens effect. Light source at
top-left with subtle white radial, warm accent bleed bottom-right,
gentle bottom shadow for volume (not 85% dark brown). Elliptical
specular highlight instead of circular. Double rim (outer hairline
+ inner inset) matching the web's box-shadow approach. Caustic
sweep reduced to 25-30% alpha (was 50-65%).
2026-06-22 00:35:21 +02:00
Bruno Deanoz
2d57afff61 input field: more breathing room, mic/send toggle
Reduced outer padding (12dp→8dp) for wider feel, increased inner
padding (8dp→10dp) and min height (36dp→42dp) for more air.
Placeholder text slightly smaller (15sp) and more subtle (0.6α).
Text line-height increased to 24sp.

Single action button: shows Mic icon when input is empty, morphs
to Send button when text is entered — like WhatsApp/iMessage.
Removed separate Call button that took space without function.
Aligned action buttons to bottom for multi-line input.
2026-06-22 00:31:33 +02:00
Bruno Deanoz
fb90577884 use model context_length for token badge instead of hardcoded 1M
Parse context_length from /api/v1/models response (KaizenModel)
and look up the active model's value for the token counter badge.
Falls back to 1M only if the model has no context_length field.
2026-06-22 00:28:16 +02:00
Bruno Deanoz
3ca1d2c18e scroll buttons, token counter, 3D user bubble
Scroll: glass arrow buttons at center-right edge, appear when
scrolled away from top/bottom. Animate scroll on tap.

Token counter: parse usage sentinel (input+output tokens) from
stream, show " Xk / 1M" badge next to model pill in top bar.
StreamState extended with inputTokens/outputTokens.

User bubble: multi-layer 3D effect — higher opacity gradient bg,
inner top highlight (accent glow), kaizenShadow level1 for depth,
thicker border gradient. Increased padding and line-height (26sp)
for better readability.
2026-06-22 00:26:31 +02:00
Bruno Deanoz
f6dc5db363 reasoning presets + sampling controls (functional, not mock)
Reasoning pill (Brain icon + dropdown): 8 presets matching web
(Sofort/Aus/Minimal/Niedrig/Standard/Mittel/Hoch/Maximal).
"Standard" = no override, "Sofort" = no reasoning + throughput.
Sends reasoningEffort + preferThroughput in ChatRequest.

Sampling pill (Tune icon + popover): per-parameter toggle +
slider for Temperature (0-2), Top-P (0-1), Top-K (1-100).
Each parameter independently on/off — only enabled ones are
sent. Accent highlight when any param is enabled.

Both controls are separate from mode pills (Bild/Suche/Video)
and work in chat + search modes, matching the web layout.

ChatRequest extended with temperature/topP/topK fields.
Old ChatSpeed enum no longer used for reasoning (kept for
backward compat with ServerConfig).
2026-06-22 00:19:14 +02:00
Bruno Deanoz
30d3f2807d wire search mode, single-select pills, remove Standard pill
Mode pills are now single-select (tap to activate, tap again to
deactivate) matching the web frontend behavior. Removed the
"Standard" pill — it's the implicit default when no mode is active.

Search mode sends webSearch: true in the ChatRequest body, which
triggers the backend's agentive web search (openrouter:web_search
or Google Search grounding). Sources and query are already parsed
by StreamConsumer and rendered via SourcesBlock/SourceChip.

Active pills now highlighted with accent color + accent border
instead of generic surface alpha, making the active state
visually obvious.
2026-06-22 00:10:14 +02:00
Bruno Deanoz
a16dfffd5c auto-lock on background + reasoning/sources tests
Clear locked chat messages when app goes to background
(onPauseOrDispose). Track viewingLockedChat state — reset on
new chat or opening a non-locked conversation.

Add tests: reasoning round-trip, null reasoning preserved,
sources round-trip, empty sources → null, reasoning+sources
together, SearchSource converter (empty/single/multi/malformed/
unknown fields).
2026-06-22 00:03:59 +02:00
Bruno Deanoz
88559bf49e biometric unlock for locked conversations
Hardware-enforced via Android KeyStore + CryptoObject:
- AES key in TEE/StrongBox with setUserAuthenticationRequired(true)
- BiometricPrompt receives Cipher as CryptoObject, unlocked only
  after real biometric auth (fingerprint/face), not bypassable
- Key auto-invalidated on biometric enrollment change

Flow: tap locked chat → BiometricPrompt → on success request
server unlock token (POST /api/v1/auth/unlock) → fetch messages
with X-Unlock-Token header → display conversation.

Dependencies: androidx.biometric:biometric:1.4.0-alpha02
2026-06-21 23:56:57 +02:00
Bruno Deanoz
278d98d125 fix input field clipped by keyboard
Add imePadding() to the bottom dock so it pushes above the soft
keyboard. Previously only navigationBarsPadding() was set, which
handles the gesture bar but not the IME — the input field was
partially hidden behind the keyboard suggestion row.
2026-06-21 23:43:13 +02:00
Bruno Deanoz
532cb8c27b persist reasoning + sources, render reasoning above content
Fix two bugs:
1. ReasoningBlock rendered below content — moved above, matching
   web frontend (reasoning first, then content, sources at bottom)
2. Reasoning and sources lost after closing chat — now persisted
   end-to-end: sent to server via SaveMessage, loaded back via
   StoredMessage, cached in Room via MessageEntity + SearchSource
   TypeConverter. Syncs correctly between web and app.

Room schema change triggers destructive migration (cache rebuild).
2026-06-21 23:39:07 +02:00
Bruno Deanoz
f3d8154052 sidebar redesign + markdown typography overhaul
Sidebar: increase glass opacity (0.92/0.88), flat conversation rows
instead of pill shapes, 3-dot context menu (rename/delete/pin/lock),
smaller profile avatar (30dp), logout integrated into user card.
Add KaizenApi.patchConversation + deleteConversation for CRUD.

Markdown: increase line-height from 23sp to 26sp (×1.625 ratio),
double paragraph spacing (6dp→12dp), generous heading margins
(18dp top, 8dp bottom), wider list item spacing — matches Gemini-
level readability.
2026-06-21 23:31:03 +02:00
Bruno Deanoz
e56e9a4c83 fix: tap anywhere outside attachment menu to dismiss it 2026-06-21 19:01:48 +02:00
Bruno Deanoz
b94a0a8845 fix: move attachment menu closer to left edge 2026-06-21 19:00:23 +02:00
Bruno Deanoz
b0a7cc37aa fix: nudge attachment menu slightly more to the left 2026-06-21 18:59:11 +02:00
Bruno Deanoz
5c646ae4a1 fix: attachment menu aligned to bottom-left above the + button 2026-06-21 18:58:22 +02:00
Bruno Deanoz
da01b57f08 fix: attachment menu — individual floating glass pills instead of card
Each option (camera, gallery, file) is its own GlassSurface pill with
frosted glass blur, round colored icon, and shadow. No wrapping card.
2026-06-21 18:57:10 +02:00
Bruno Deanoz
43b2a72c96 fix: attachment menu floats above pills instead of pushing them up
Moved AttachmentMenu out of the bottom dock Column into its own
floating overlay positioned above the dock with padding(bottom=140dp).
Pills and input stay in place, menu hovers over them.
2026-06-21 18:55:36 +02:00
Bruno Deanoz
5fbeded849 fix: attachment menu — vertical glass panel with round colored icons
Replaced the horizontal circle buttons with a vertical GlassSurface
panel. Each row has a 40dp round tinted icon + label, matching the
app's glass design system. Slides up above the input with animation.
2026-06-21 18:53:33 +02:00
Bruno Deanoz
e1480fbb57 feat: modern attachment menu with round colored icon buttons
Replaced the generic DropdownMenu with a row of three large (56dp)
circular icon buttons: Camera (accent), Gallery (indigo), File (emerald).
Slides in/out with animation above the chat input. Toggle on +.
2026-06-21 18:50:58 +02:00
Bruno Deanoz
09489318bf feat: attachment picker menu — camera, gallery, and file options
Replaced the generic file picker with a dropdown menu offering three
options: take photo (camera preview), pick from gallery (image/*), or
choose any file (*/*). Camera captures are compressed as JPEG and
uploaded like any other file. Upload logic extracted into shared
uploadPickedFile helper.
2026-06-21 18:45:41 +02:00
Bruno Deanoz
5c54b9b9e3 fix: rewrite ToolEventsBlock to match web frontend logic
Parse tool events into thinking/analyzing/inProgress/completed states
instead of showing all as generic "Tool" rows. Handles type "thinking"
(shows "Verarbeitet…"), "analyzing" (shows filename), "start"/"end"/
"error" with proper icons and elapsed time. Empty/thinking-only events
no longer show a confusing wrench icon.
2026-06-21 18:40:19 +02:00
Bruno Deanoz
cad445f87c fix: replace all remaining hardcoded German strings with stringResource
ModelSheet, ChatComponents, SettingsScreen had hardcoded strings like
"Ausgewählt", "Modell auswählen", "Zurück" etc. instead of using the
existing string resources. Now all UI text uses stringResource(R.string.xxx)
for proper de/en localization.
2026-06-21 18:33:41 +02:00
Bruno Deanoz
d468ec893f fix: wire theme switching — replace all hardcoded Amber colors with LocalKaizenAccent
All 7 files that imported Amber/AmberDeep/OnAmber/BlobX now read from
LocalKaizenAccent.current instead. Theme switching in settings actually
changes the UI now: message bubbles, send button, orb, blobs, sidebar
highlights, model sheet, stream blocks, login screen, and settings
toggles all respond to the selected theme.
2026-06-21 18:29:42 +02:00
Bruno Deanoz
918659422b feat: background prefetch of all conversation messages for offline access
On app start, after syncing the conversation list, prefetch messages
for any conversation not yet cached in Room. This ensures all chats
are readable offline, not just previously opened ones.
2026-06-21 18:16:51 +02:00
Bruno Deanoz
fe5e93cdfa fix: settings option rows — stack title above controls instead of side by side
SettingsOptionRow was a horizontal Row cramming icon + title + wide
controls (theme circles, appearance pills) into one line. The text
got squeezed to near-zero width, breaking character by character.
Changed to vertical layout: title/description on top, controls below.
2026-06-21 17:46:18 +02:00
Bruno Deanoz
e4c6e7f763 fix: center EmptyHero vertically between top bar and bottom dock
Orb + greeting + headline were pinned to the top with a fixed spacer,
leaving the bottom half empty. Now vertically centered with padding
that accounts for the floating top bar (76dp) and bottom dock (160dp).
2026-06-21 17:41:19 +02:00
Bruno Deanoz
3560665e83 fix: mode pills are now multi-select toggles instead of single-select
Modes like Sampling, Bild, Suche can be combined — they're not mutually
exclusive. Changed from single Int to Set<Int> with toggle logic.
2026-06-21 17:39:47 +02:00
Bruno Deanoz
78f03ada99 fix: clean up mode pills — remove muddy amber tint, use solid surface fills
Active pills now use cs.surface at high opacity instead of transparent
Amber overlay. Removed amber from borders and icons, unified to neutral
glass border. Active state distinguished by higher fill + semibold weight.
2026-06-21 17:37:28 +02:00
Bruno Deanoz
5804b337d9 fix: gradient scrims on top/bottom floating bars, increase ModelPill opacity
Content was bleeding through the floating top bar (model pill + menu)
and bottom dock (mode pills + input) because they had no background.
Added vertical gradient scrims to both areas and bumped ModelPill
surface alpha from 0.7 to 0.92.
2026-06-21 17:33:43 +02:00
Bruno Deanoz
76ab8bfde9 feat: reasoning, sources, and tool-call display
StreamConsumer.Incremental now returns StreamState (content, reasoning,
tools, query, sources) instead of just visible text. All sentinel sections
are parsed and structured:
- \x01 REASONING: captured as separate string
- \x02 SOURCES: parsed as List<SearchSource> (title, url, snippet)
- \x03 TOOL: parsed as List<ToolEvent> (type, name, args, result, elapsed)
- \x04 QUERY: captured as search query string

UI components:
- ReasoningBlock: collapsible "Gedankengang" with expand/collapse animation
- ToolEventsBlock: shows tool-call steps with status icons (start/end/error)
- SourcesBlock: web search sources as compact cards with domain extraction

Message model extended with reasoning, tools, query, sources fields.
KaizenApi.chat() returns Flow<StreamState> instead of Flow<String>.
ChatScreen wires all fields to Message during streaming.

Fixed: empty append() on Incremental no longer prematurely ends tool phase.
StreamConsumerTest rewritten to verify all parsed sections.
2026-06-21 15:35:46 +02:00
Bruno Deanoz
736bb4c206 feat: persistent settings + server sync + language selector
Settings now persist across app restarts via SecureStore:
- Theme (KaizenThemeId), appearance (Light/Dark/System), username, locale
- All loaded on SettingsViewModel init, saved on every change

Server sync: fetchMe() returns name + email from /api/v1/me,
populates SettingsViewModel on login and each foreground resume.

Language selector: System / Deutsch / English pill toggle in Settings.
Locale preference stored; runtime switching deferred to next iteration.

MeResponse expanded to parse name + email (was defaultModel only).
SettingsViewModel tests updated for store-less default state.
2026-06-21 15:26:14 +02:00
Bruno Deanoz
9e4eaf2297 feat: Kaizen orb app icon — light/dark backgrounds, monochrome, splash
Adaptive icon with amber orb on warm off-white (light) / Obsidian (dark).
Multi-layer radial gradients for the orb: body, depth shadow, specular
highlight, warm bounce light, crisp rim. Monochrome layer for Android 13+
themed icons. Splash screen uses orb foreground (API 31+).

Removed all old raster WebP icons — vector-only via mipmap-anydpi.
2026-06-21 15:22:28 +02:00
Bruno Deanoz
515a71efb5 feat: Inter Variable font, theme picker wired end-to-end, settings i18n
Inter Variable (v4.1, 859 KB): full type scale with 9 weight axes
(W100–W900), negative tracking at display sizes, positive at labels.

Theme picker: SettingsViewModel now uses KaizenThemeId from the design
system (AMBER/BLAU/MONO/AURORA). MainActivity wires selectedTheme +
appearanceMode to KaizenTheme composable — switching themes in Settings
instantly propagates to the entire app.

No server sync needed — web stores theme in localStorage, app stores
locally. Backend /api/v1/me has no uiTheme field in the DB.

Settings strings migrated to string resources (de + en).
SettingsViewModelTest updated for new enum values + System default.
2026-06-21 15:17:17 +02:00
Bruno Deanoz
f83d4b4e17 fix: Oklab round-trip precision — Double math + corrected LMS matrix
Root cause: Float32 accumulated ~0.017 error across 6 matrix multiplications
(RGB→LMS→Lab→Lab→LMS→RGB), producing negative linear-green values that got
clamped to 0 by toSRGB().

Fix: all matrix math now uses Double. Forward LMS matrix corrected to match
Björn Ottosson's reference implementation. Variable shadowing eliminated.

All 3 OklabTest cases now pass (were failing since initial commit).
2026-06-21 15:10:25 +02:00
Bruno Deanoz
e3d624c961 feat: orb uses rememberTiltState(), update CLAUDE.md with i18n + migration status
KaizenOrb now uses centralized rememberTiltState() (GAME_ROTATION_VECTOR,
lifecycle-aware, battery-saver-aware) instead of inline sensor code.
Breath animation uses Durations tokens from motion system.

CLAUDE.md: updated design system section with GlassSurface correction
(no backdrop-filter on Android), migration status (all components done),
i18n section documenting the de/en string resource architecture.
2026-06-21 15:04:31 +02:00
Bruno Deanoz
0237eae1e6 feat: i18n — extract all UI strings to resources (de + en)
German (default) in values/strings.xml, English in values-en/strings.xml.
Matches web app's next-intl de/en setup.

Migrated: greeting, hero headline, chat input, error messages, mode pills,
suggestion pills, sidebar (brand, search, dates, logout, settings),
model sheet, login screen (welcome, fields, errors, submit button),
conversation labels (locked, pinned).

ChatModels: Suggestion/ChatMode now use resource IDs instead of hardcoded strings.
greeting() is now @Composable (uses stringResource).
ModePillsRow: selected state uses resource ID (Int) instead of String.
Date labels (HEUTE/GESTERN/FRÜHER) localized via DateLabels data class.
2026-06-21 15:02:16 +02:00
Bruno Deanoz
94c0f85fb7 feat: migrate all UI components to design system v2
- Sidebar: GlassSurface + KaizenShapes (lg, sm, md, pill, circle)
- ChatInput: GlassSurface with input tier opacity
- ChatScreen: menu button uses GlassSurface, error banner uses KaizenShapes
- EmptyHero: suggestion chips removed on mobile (DESIGN.md §11.1)
- LoginScreen: GlassFields use KaizenShapes.lg
- ModelSheet: KaizenShapes (xl, md, xs, circle)
- SettingsCard: GlassSurface with card tier opacity
- SettingsScreen: back button uses GlassSurface
- SettingsComponents: all CircleShape/RoundedCornerShape → KaizenShapes
- Markdown: copy button uses KaizenShapes.circle
- GlassSurface: removed misleading Modifier.blur (Android has no backdrop-filter)
- TypingDots: uses motion duration tokens
- Fixed deprecated ArrowBack icon → AutoMirrored variant
2026-06-21 14:50:11 +02:00
Bruno Deanoz
4d71475995 fix: derive all theme colors from web CSS files (OKLCH → P3 RGB)
Amber blobs were cold (blue/indigo) but web uses warm (gold/coral/honey/teal).
Mono had slate chroma but web is pure achromatic (zero chroma).
Aurora dark primary was magenta but web swaps to gold in dark mode.
All four themes now pixel-match the web app's OKLCH values.
2026-06-21 14:41:23 +02:00
Bruno Deanoz
6465b47bbb feat: design system v2 — P3 colors, squircle shapes, glass surfaces, multi-theme, motion tokens
Foundation for the premium UI overhaul:
- P3 DisplayP3 color pipeline (all tokens in wide gamut)
- SquircleShape (iOS-style superellipse with G2 continuity)
- KaizenShapes token system (xs/sm/md/lg/xl/pill/circle)
- Multi-layer ShadowStack system (level 0–4)
- GlassSurface composable with RenderEffect blur + API fallback
- Motion tokens (easing curves, spring specs, durations)
- Tilt sensor state (GAME_ROTATION_VECTOR, lifecycle-aware)
- 4-theme system (Amber/Blau/Mono/Aurora) with AccentScheme
- ThemeRegistry + CompositionLocal for accent propagation
- Unit tests for colors, shapes, shadows, themes
2026-06-21 14:32:25 +02:00
Bruno Deanoz
203316b1c3 fix: enable encodeDefaults so message persistence actually works
kotlinx.serialization defaults to not encoding properties with default
values. SaveMessage.kind = "chat" was never sent, causing the backend's
sanitizeSaveableMessage() to reject every save with 400. This also
broke title generation since it was gated behind a successful save.
2026-06-21 13:43:24 +02:00
Bruno Deanoz
9759d478a7 feat: offline-first cache layer with Room
Room 2.8.4 as local SQLite read-cache for conversations and messages.
Sidebar and chat history load instantly from cache, background refresh
syncs with the server. Streaming stays in-memory, flushed to Room after
completion. Includes ChatViewModel, repositories, mappers, TypeConverters,
and unit tests.
2026-06-21 12:50:46 +02:00
Bruno Deanoz
d8dcb25e3d feat: attachment support — display, upload, and send files in chat
- Parse and render message attachments from loaded conversations (images
  inline via OkHttp + in-memory cache, other kinds as icon chips)
- Wire up the + button with Android file picker (ActivityResultContracts)
- Upload files to /api/v1/upload with progress tracking and preview chips
- Send attachments with chat requests and persist them in saved messages
- Markdown: horizontal rules, blockquotes, links, code block copy button
2026-06-20 00:36:07 +02:00
Bruno Deanoz
4616ef2ba1 feat: Markdown rendering with syntax-highlighted code blocks
Native Compose Markdown renderer replaces raw Text() for assistant
messages. Supports headings, bold/italic, inline code, fenced code
blocks with keyword highlighting (15+ languages), ordered/unordered
lists. Streaming cursor integrates seamlessly into the last block.
2026-06-19 17:21:01 +02:00
Bruno Deanoz
423abc03af fix: show specific error diagnostics instead of generic connection failure
Replace null-returning fetchModels/fetchConversations with FetchResult<T>
sealed interface. Error banner now shows the exact cause (HTTP status,
DNS, SSL, timeout, JSON parsing) instead of "Verbindung zum Server
fehlgeschlagen".
2026-06-19 17:10:05 +02:00
Bruno Deanoz
827a2f307c fix: streaming performance, error handling, and API diagnostics
- Eliminate recomposition hell: isolate auto-scroll via derivedStateOf + snapshotFlow
- Preserve partial response on connection drop instead of replacing with error
- O(1) incremental stream parser (StreamConsumer.Incremental) replacing O(N²) re-scan
- Parallel conversation creation via async/await to reduce TTFT
- TCP/TLS prewarm on app resume via LifecycleResumeEffect
- Surface API failures with Log.w and UI error banner instead of silent emptyList()
- Fix nullable KaizenModel.name crash in ModelSheet
2026-06-19 15:34:59 +02:00
Bruno Deanoz
d81c6ffda7 fix: make KaizenApi exceptionally robust against malformed JSON or parsing failures 2026-06-19 09:06:55 +02:00
Bruno Deanoz
7399c73e6a feat: Redesign model selection sheet with OpenRouter/Vertex badging & offline-first caching 2026-06-19 09:00:58 +02:00
Bruno Deanoz
ea5e625f0f feat(chat): wire sidebar to real conversations + persist history
Sidebar now lists the user's conversations from GET /api/v1/conversations
(date-grouped, pinned first, active highlighted) instead of mock data. Sending
creates a server conversation on the first message, persists each exchange via
POST .../messages, and auto-titles new conversations. Tapping a chat loads its
stored messages; new-chat/logout reset the active conversation. Linear model
(no branching); locked chats are skipped (no in-app unlock yet).
2026-06-19 07:58:07 +02:00
Bruno Deanoz
d2f269f33a feat(chat): model + speed picker (top pill + bottom sheet)
Top pill shows the active model; tapping opens a bottom sheet with a
response-speed segment (Instant/Normal/Max -> reasoningEffort + throughput),
model search, favorites, provider grouping and a Vertex hoster badge.
Model/speed/favorites persisted in the encrypted store; chat now sends the
additive reasoningEffort/preferThroughput v1 fields.
2026-06-19 07:43:59 +02:00
Bruno Deanoz
aa0a97b959 fix(ui): dither gradients to kill 8-bit sRGB banding on orb + mesh 2026-06-19 07:27:33 +02:00
Bruno Deanoz
44c17b6f9f feat: wire chat to the real Kaizen backend (in-app login + streaming)
Replaces the Phase-0 fakeReply mock with the real v1 API. Deployment-agnostic:
one binary, one Authorization: Bearer contract, server-URL field switches between
the official instance and any self-host.

- auth/LoginScreen: server-URL + email + password -> POST /api/v1/auth/token
- net/SecureStore: kzn_ token in EncryptedSharedPreferences (KeyStore AES-256-GCM)
- net/SessionViewModel: snapshot-state login/logout routing, restores on launch
- net/KaizenApi: POST /api/v1/chat streaming + GET /api/v1/me for the default model
- net/StreamConsumer: Kotlin port of lib/chat/stream-consumer.ts (JVM-unit-tested)
- deps: okhttp, kotlinx-serialization, security-crypto; INTERNET permission

Code-complete; not yet built/run on device (no Android SDK on the dev Mac).
2026-06-19 01:04:55 +02:00
Bruno Deanoz
8a527451d7 fix(ui): resolve ColumnScope, Oklab.Lab, and missing layout imports in SettingsComponents.kt 2026-06-18 17:46:05 +02:00
Bruno Deanoz
779796f825 feat(ui): connect modular SettingsScreen, complete SettingsComponents, and wire up dynamic sidebar avatar 2026-06-18 17:44:01 +02:00
Bruno Deanoz
d0cb89cf6e feat(ui): add clean SettingsViewModel and comprehensive ViewModel JVM unit tests 2026-06-18 17:42:00 +02:00
Bruno Deanoz
0999e154cd test(ui): add comprehensive Oklab color-space unit test suite 2026-06-18 17:36:30 +02:00
Bruno Deanoz
57fa02bd6f feat(ui): implement Display P3, Ultra HDR support, and perzeptive Oklab color interpolation 2026-06-18 17:25:12 +02:00
Bruno Deanoz
41a21c30c8 fix(ui): import background and border Modifiers in ChatScreen.kt 2026-06-18 17:18:21 +02:00
Bruno Deanoz
7235cd7bc1 fix(ui): import isSystemInDarkTheme in ChatScreen and correct padding signature in Sidebar 2026-06-18 17:16:10 +02:00
Bruno Deanoz
37e424df3e feat(ui): implement gorgeous floating glassmorphic sidebar and rich 3D ambient shadows for suggestions, pills, and input 2026-06-18 17:13:13 +02:00
Bruno Deanoz
2c3d057641 fix(ui): dynamically shrink EmptyHero bottom spacer on keyboard open to keep greeting visible 2026-06-18 17:03:55 +02:00
Bruno Deanoz
7b012d1041 fix(ui): remove incorrect blur from sphere Canvas to restore razor-sharp edges and solid crystal glass definition 2026-06-18 17:01:28 +02:00
Bruno Deanoz
b03f1773fe fix(ui): add missing ExperimentalLayoutApi import to ChatComponents.kt 2026-06-18 16:59:43 +02:00
Bruno Deanoz
c3fb668ea1 feat(ui): sculpt KaizenOrb into a highly realistic 3D sphere with heavy volume shadows, and remove the redundant top header bar 2026-06-18 16:55:49 +02:00
Bruno Deanoz
7a95f7735c feat(ui): implement masterclass KaizenOrb with native 3D gyroscope parallax refraction and double conic caustics 2026-06-18 16:52:41 +02:00
Bruno Deanoz
f8498adc27 fix(ui): add @OptIn(ExperimentalLayoutApi) for FlowRow layout compiler error 2026-06-18 16:43:52 +02:00