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.
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.
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.
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.
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).
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.
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.
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
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.
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.
- 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
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".
- 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
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).
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.
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).