Commit Graph
531 Commits
Author SHA1 Message Date
M57andM57 53c8eca714 feat: adopt side-questions via prompt.btw and /btw command in Chat (fixes #1015) (#1026)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-31 15:54:09 +03:00
M57andM57 cfe3e736fd chore: upgrade ktlint to 1.8.0 and format codebase (#1024)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-31 12:27:40 +03:00
M57 ad838f696e chore: add missing imports and fix test closing brace 2026-08-31 11:24:45 +03:00
Hy4ri d8e3992c33 fix: memory screen reset actions serialization error (fixes #1020) 2026-08-31 11:10:26 +03:00
M57 cdcecb9734 feat(profiles): add local on-device profile hiding and filtering 2026-08-30 20:51:06 +03:00
M57andM57 5c8ab7ed6c feat(bots): smart streaming auto-scroll and scroll-to-bottom FAB in group chat (#1013)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-30 00:21:02 +03:00
M57andM57 97b6641d84 feat(bots): emergency stop button and /stop slash command in group chat (#1012)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-30 00:02:16 +03:00
M57andM57 bd4a73dd48 feat(bots): horizontal swipe between bots and groups tabs with drawer gesture gating (#1011)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-29 23:48:08 +03:00
M57andM57 174f8dee7d feat(bots): selectable text, timestamps, and copy buttons in group chat (#1009)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-29 19:24:51 +03:00
M57andM57 fbe602cb4f feat(bots): lightweight tool execution indicators in group chat (#1008)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-29 19:11:14 +03:00
M57andM57 0d09f43469 feat(bots): reactive multi-agent group chat orchestration with CAS leasing (#1007)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-29 18:44:08 +03:00
M57andM57 7e24f5eba3 fix(bots): sync cross-device group chats and roster from desktop projection (#1006)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-29 13:56:40 +03:00
M57andM57 5bc6e5f855 feat(settings): in-app chat font size setting (#1004) (#1005)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-29 11:36:56 +03:00
M57andM57 db3f8c736e feat(markdown): support variable-length code fences and nested codeblocks (#1003)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-28 20:27:17 +03:00
M57andM57 7a2c453f58 fix(history): guard fluid pagination against infinite network loop on small session lists (#1002)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-28 19:44:28 +03:00
M57andM57 2332bdd5d5 feat(nav): root swipe-back on chat with double-back exit and subpage hierarchy (#1001)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-28 18:38:01 +03:00
M57andM57 f04d878358 feat(bots): add multi-agent group chat collaboration room (#998)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-28 17:50:37 +03:00
M57 0cdccad855 fix(bots): create clean profile without cloning default soul and custom skills (#997) 2026-08-27 19:06:58 +03:00
M57andM57 14fde03083 feat(image): migrate to Coil 3.6.0 with OkHttp network fetcher (#996)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-27 15:13:18 +03:00
M57andM57 4d9dfb99db feat(db): migrate to androidx.room3 3.0.2 with SQLCipherDriver (#994)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-27 13:47:32 +03:00
AlexDev_ 0ae5ed4db2 feat(chat): stabilize multi-image attachment picker (#986) 2026-08-27 10:50:55 +03:00
AlexDev_ c7d7abf4a1 fix(chat): prevent timeline prefetch ANRs (#985) 2026-08-27 10:47:04 +03:00
AlexDev_andM57 ce8fdc45bd fix(notification): open tapped chat at bottom (#984)
Co-authored-by: M57 <41645758+Hy4ri@users.noreply.github.com>
2026-08-27 10:41:08 +03:00
M57 9c91df56f3 fix(theme): replace Color.Gray fallback with Color.Unspecified in palette color pickers
Resolves checkColorLiterals hardcoded color guard failure in CreateBotDialog and EditBotBottomSheet.
2026-08-27 02:38:05 +03:00
M57 2fed891d78 feat(ui): add Bots and Groups tab strip with GroupCard and disband action (#987) 2026-08-27 01:53:54 +03:00
M57 6ca248d28b feat(ui): add EditBotBottomSheet with avatar editor and bot deletion (#983) 2026-08-27 01:53:15 +03:00
M57 274120cc86 feat(ui): add CreateGroupChatDialog for multi-bot room creation (#982) 2026-08-27 01:52:34 +03:00
M57 d4d6a04afc feat(ui): add CreateBotDialog to BotsScreen with avatar customizer (#981) 2026-08-27 01:51:43 +03:00
M57 b422ead921 feat(chat): add @bot mention autocomplete to ChatComposer (#980) 2026-08-27 01:51:10 +03:00
M57 4e666b49a8 feat(chat): intercept /new in canonical Bot Chat sessions to /compact (#979) 2026-08-27 01:50:43 +03:00
M57 48c9039e02 feat(ui): add BotsScreen and BotsViewModel with active presence strip (#977) 2026-08-27 01:49:31 +03:00
M57 6af349cdf2 feat(ui): add reusable Material3 BotAvatar component (#974) 2026-08-27 01:49:08 +03:00
M57 c028077647 feat(models): support bot metadata, canonical sessions, and group sync in Profile models (#973) 2026-08-27 01:48:43 +03:00
M57andM57 1f1ebf3597 feat(update): redesign in-app updater with smart cooldown, release notes, and in-place dialog (#971)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-26 07:42:41 +03:00
M57andM57 460472f631 fix(chat): queue prompts while session creation is in flight (issue #969) (#970)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-26 06:35:44 +03:00
M57andM57 14b9c1d1d3 fix(chat): support multi-level nested bullet, task, and ordered markdown lists (#965) (#968)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-25 17:41:47 +03:00
M57andM57 9217f9f8b6 fix(sessions): tolerate integer and string boolean formats for pinned field (#966) (#967)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-25 17:05:14 +03:00
M57 ece0288ca8 feat(update): surface backend update receipt in action popup (#958) (#964)
Adopt GET /api/hermes/update/receipt and append its summary to the
end of the hermes-update action progress popup after it finishes.
Additive only: existing checkHermesUpdate + connection-retry polling
is untouched. 404 (old backend) is handled gracefully.

- add UpdateReceiptResponse data model matching backend schema
- add getUpdateReceipt() to HermesApiService
- ActionProgressController gains trailingLines + pushTrailingLines
- ActionProgressDialog renders trailingLines after the log
- SystemViewModel fetches receipt on action finish and formats it
- extract pure formatUpdateReceiptLines for mockk-free testing
2026-08-24 01:35:59 +03:00
M57andM57 5fd732be9b fix(chat): preserve ordered list numbering in loose lists (#959) (#963)
ORDERED_RE only captured item text and discarded the source number, and the
parse loop broke on the first blank line — so loose numbered lists (blank lines
between items) re-indexed every item to 1.

- capture the prefix number and emit it as MdBlock.Ordered.index
- treat a blank line as list-internal when the next non-blank line is another
  ordered item, so loose lists stay one ordered list
- keep sequential numbering for tight lists (1,2,3) and honor custom starts

add regression tests in MarkdownTextFeatureTest (loose, custom-start, tight).

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-24 00:16:27 +03:00
M57andM57 764cb3455f fix(auth): await cookie scope hydration on cold start (#961) (#962)
* fix(auth): await cookie scope hydration before ticket mint and request loading (#961)

* refactor(auth): scope latch activation to active store hydration

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-23 23:57:37 +03:00
M57andM57 7540f7228a fix(chat): show send button for attachment-only sends (#956) (#956)
The AnimatedContent gate on the composer's send button required
inputFieldValue.text.isNotBlank() in addition to canSend, so attaching an
image or file with an empty input hid the button entirely — the attachment
could never be sent on its own. ChatInputPolicy.canSend already allowed
attachment-only sends and ChatViewModel.sendMessage already accepted blank
text with pending attachments, but the button never rendered to trigger it.

Drop the stale text gate so the send affordance tracks canSend (non-empty
text OR pending attachments, plus connection). The backend handles
image-only turns natively: prompt.submit with empty text + queued images
routes through build_native_content_parts, which wraps the pixels with an
[Image attached at: ...] hint.

Add a Compose UI regression test asserting the send button is displayed
with a pending image attachment and an empty input.

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-22 21:58:53 +03:00
M57andM57 69578e7818 refactor: extract media + server-message-mapping from ChatViewModel (#955)
* refactor: extract ChatMediaDelegate from ChatViewModel (verbatim move)

* refactor: extract mapServerMessages to ChatServerMessageMapper (verbatim move)

* style: remove orphaned comments after media extraction, ktlint format

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-22 20:44:43 +03:00
M57andhy4ri bb400881e6 ws: harden HermesWsClient (stale-socket watchdog, JSON ticket parse, dead-branch removal) (#954)
* ws: watchdog cancels stale sockets to trigger reconnect

* ws: parse ws-ticket response with kotlinx.serialization

* ws: remove unreachable buffer-overflow warning

* style: ktlint format ws client tests

---------

Co-authored-by: hy4ri <hy4ri@users.noreply.github.com>
2026-08-22 18:49:50 +03:00
M57andM57 a11761f120 fix(chat): skip empty assistant tool-call placeholder rows in REST transcript (#953)
* fix(chat): skip empty assistant tool-call placeholder rows in REST transcript

Non-reasoning models (e.g. gemini-3.5-flash) store a tool-call turn as an
empty assistant row (content = "", no reasoning) with a tool_calls list.
The REST transcript returns these verbatim, and mapServerMessages was only
filtering empty assistant rows that carried reasoning text (the issue #771
thinking-model case). With no reasoning, the filter check failed and the
placeholder rows fell through — rendering as empty bubbles with Copy buttons
in the chat list.

Fix: skip ANY empty assistant row regardless of whether reasoning is present.
If it carries reasoning, stash it for folding (issue #771 path unchanged).
If it doesn't, it's a pure tool-call placeholder — silently drop it.
No user-visible content is lost: these rows never held displayable text.

Verified on device (hermes37 emulator): session 'Friendly greeting #9' had
10 ghost empty bubbles; after fix the transcript shows only real messages.

Issue: #956

* fix(chat): blank-content guard in FullBleedAgentMessage prose renderer

Defense-in-depth for the ghost-bubble fix (#953): never render an empty
prose block (blank text + lone Copy button) if a placeholder row ever
slips past upstream mapping. Streaming messages keep rendering so the
live cursor survives until the first delta lands.

Verified on device: cold start of 'Friendly greeting #9' renders only
real content — no empty bubbles.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-22 13:56:41 +03:00
M57 5153e1572f fix(chat): dedupe user bubbles enriched with attachment ref lines (#952)
Sending an image/file renders the user message twice: the optimistic
bubble carries the raw caption while the backend persists an enriched
copy with "@image:" / "@file:" ref lines and a "[screenshot]" marker
(_build_image_ref_message / run_agent multipart flattening). The trimmed
exact-content compare in sameLogicalMessage fails on that drift and the
prefix fallback gate (>=40 chars) rejects short captions, so the REST
transcript sync appends the REST copy as a second bubble.

Compare USER rows on the caption with attachment-injection lines
stripped (stripAttachmentRefLines). USER-only so assistant text that
legitimately mentions these tokens is never collapsed.

Device-verified on the hermes37 emulator: image send + transcript
resync renders a single user bubble, restart/reload stays clean.
Unit: ChatToolDedupeTest 32/32.
2026-08-22 12:33:48 +03:00
M57andM57 e9f2c726e4 feat(chat): surface per-model reasoning capabilities (#946) (#951)
Add ModelCapabilities (capabilities: Map<String, ModelCapabilities>?)
to ModelProvider matching backend /api/model/options shape
{model: {fast, reasoning, can_disable_reasoning}}. Nullable/defaulted
for backward compat with old backends (ignoreUnknownKeys+SnakeCase).

Chat composer: gate reasoning dropdown on can_disable_reasoning and
supportsReasoning — "None" disabled with "reasoning always on" hint
when can_disable==false, chip disabled with "No reasoning" when
reasoning==false. Missing keys offer full scale.

Sync currentModelCapabilities from cachedModelOptions on
SessionInfo, resume, preload/refresh, and optimistic /model swap;
clear on resetSessionState. Used by ComposerToolbar via ChatScreen.

Model picker: surface same hints in ModelPickerDialog (pinned+all)
and ModelScreen ProviderCard.

Verified: ktlintCheck OK, compileDebugKotlin OK (rerun-tasks).

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-21 21:24:01 +03:00
M57 4549410a45 chore(ui): replace emoji with Material icons across chat UI (#944)
* chore(ui): replace emoji with Material icons across chat UI

Removes emoji glyphs and swaps them for real Material icons / plain text:

- ToolSchemaRegistry: iconEmoji String -> ImageVector icon for all ~35 tools;
  summaryPrefix emoji stripped (terminal keeps '$ '); consumer renders Icon().
- ToolBubble: collapsed summary now shows Icon(config.icon) instead of an emoji
  Text prefix; web-search url drops the link emoji; warning badge uses
  Icons.Filled.Warning instead of a '⚠' Text glyph.
- ChatBubble SelfImprovementReviewCard: 🛠/🧠 String -> Build/Psychology Icon.
- ChatViewModel / ChatWsEventReducer: drop ⚠️/✅ prefixes from user-facing
  status/error messages (severity already conveyed by color/styling).
- SubagentInspectionSheet: ✅/🔄/❌/⭕ status markers -> CheckCircle/Autorenew/
  Cancel/RadioButtonUnchecked (tinted); ElectricBolt section headers ->
  FormatListBulleted + Groups.
- strings.xml (en/zh/ko): drop '✓' from the Copied label.

ktlint clean; compileDebugKotlin SUCCESSFUL. Remaining chat files
(ToolViewBuilder, ChatComposer, MessageCards, ChatReactions, ComposerToolbar,
FullBleedAgentMessage) tracked in follow-up commits on this branch.

* test: align unit tests with emoji-free summary + status messages

composeToolSummaryLines dropped the emoji parameter and no longer prepends
an emoji glyph to the collapsed summary line, so update ToolSummaryLinesTest
call sites + expected strings accordingly.

SlashCommandDispatchRpcTest asserted the old '⚠️ /cmd: ...' error messages;
those prefixes were removed in the UI cleanup, so drop them from the
expected strings.

ChatWsEventReducerTest emoji usages are model-output fixtures, not assertions
on changed code paths — left untouched.
2026-08-21 00:07:37 +03:00
M57 fad49b7b66 feat(chat): compact sticky task progress chip (#942) (#943)
* feat(chat): compact sticky task progress chip (#942)

Re-add a glanceable progress strip above the chat timeline while work is
active. Distinct from the inline TodoTaskCard (permanent timeline record):
this chip is the temporary "what is happening right now" surface.

- Shows only while todos are incomplete or a subagent is running.
- Bounds to the same hydrated state.todos / subagentIndicators the card
  uses -- NOT the old broken state.todos gate removed in #811.
- Label: "Tasks 2/5 · <in-progress task> · N agent" (agent count when relevant).
- Tap opens SubagentInspectionSheet + resumes bottom-follow.
- Auto-hides when all work completes/cancels; rebuilds on session resume.
- i18n strings added (en/zh/ko); ktlint clean; compileDebugKotlin + unit
  tests green.

* refactor(chat): drop inline TodoTaskCard, keep single sticky progress chip

Per follow-up to #942: the inline todo card in the chat view is removed. The
sticky TaskProgressChip above the timeline is now the only glanceable surface;
the tool message itself remains the permanent record.

- Remove inline TodoTaskCard early-return in ToolBubble.kt (todo tool output
  now renders like any other tool bubble).
- Delete TodoTaskCard.kt + TodoTaskCardTest.kt (SecurityRiskChip lives in
  ToolBubble.kt, so risk display is unaffected).
- Redesign chip: replace the ElectricBolt icon with a real
  CircularProgressIndicator (no emoji), keep ChevronRight affordance.
- Label handles the no-todos case (subagents only): 'N agent running'.
- Drop dead todo_task_count string; add task_progress_agent_running (en/zh/ko).

ktlint clean; compileDebugKotlin + chat unit tests green.

* chore: clear pre-existing compile warnings

- HermesWsClient.kt: drop unnecessary safe call on non-null ResponseBody
  (resp.body.string()) and the now-dead 'body != null' guard it implied.
- SessionsScreen.kt: replace deprecated Icons.Filled.CallSplit with the
  AutoMirrored variant (match existing Send usage in this file).

ktlint clean; compileDebugKotlin SUCCESSFUL with no warnings.

* chore(chat): replace emoji status markers with Material icons in inspection sheet

The sticky progress chip opens SubagentInspectionSheet on tap; that sheet used
emoji Text glyphs for status. Replaced with real Material icons:

- Todo rows: completed -> CheckCircle (success), in-progress -> Autorenew
  (primary), cancelled -> Cancel (warning), pending -> RadioButtonUnchecked
  (muted).
- Subagent rows: complete -> CheckCircle (success), failed -> Cancel (error),
  running -> Autorenew (primary).
- Section headers: ElectricBolt -> FormatListBulleted (agent plan) + Groups
  (subagents).

Icons tinted via LocalHermesStatusColors. No emoji remain in the chip's tap
surface. ktlint clean; compileDebugKotlin SUCCESSFUL.

* fix(lint): drop orphaned todo_task_count translation in ko

Removing the string from the default (en) locale left a stale Korean
translation, which tripped Lint's ExtraTranslation error and aborted
lintDebug. Removing the orphan so default + translations stay in sync.

android-lint now BUILD SUCCESSFUL (0 errors).

* fix(chat): address #943 review - progress count, resume hydration, chip tests, plurals

- TaskProgressChip: current-task number is now the first unfinished,
  non-cancelled todo's 1-based position (active index + 1), so a completed
  task 1 + in-progress task 2 reads as 2/5 instead of 1/5 (review point 2).
- Deterministic resume hydration: hydrate todos directly from cached Room
  messages and from the merged REST transcript inside ChatViewModel load
  paths, in addition to the WS reducer, so a resumed session rebuilds the
  chip without waiting for a follow-up event (review point 3).
- Extracted computeChipDisplay / shouldShowProgressChip pure functions and
  added TaskProgressChipTest covering active-task progress, completed/cancelled
  hide, subagent-only state, and transcript-resume hydration (review point 4).
- agent count strings are now Android plurals (task_progress_agents /
  task_progress_agent_running) in en/zh/ko to avoid '2 agent' (review point 5).

Co-Authored-By trailer intentionally omitted per repo convention.

* fix(chat): derive chip number and content from the same selected task

computeChipDisplay selected the task number from the first unfinished/
non-cancelled todo but the content independently from the first in_progress
todo. With a pending task 1 + in_progress task 2 this produced 'Tasks 1/N ·
<task2 content>'. Now both values come from one selected item (prefer
in_progress, fall back to first pending/non-terminal).

Adds two focused tests: same-item selection, and pending-only fallback.
2026-08-21 00:03:50 +03:00
M57andHy4ri 59bcbdfe3c build: Android 17 (API 37) + okhttp 5.5.0 + LAN local-network permission gate (#941)
* build: compileSdk 36 -> 37 + okhttp 5.4.0 -> 5.5.0

Take okhttp 5.5.0 (folds in dependabot PR #940) which hard-requires
compileSdk 37 via AAR metadata. targetSdk stays 36 for now.

* build: targetSdk 36 -> 37 (Android 17) + ACCESS_LOCAL_NETWORK gating

Opt into Android 17 runtime behavior. The app connects to a gateway
(default 127.0.0.1:9119, plus LAN + remote), so targeting 37 forces the
new ACCESS_LOCAL_NETWORK permission for LAN hosts.

- AndroidManifest: declare ACCESS_LOCAL_NETWORK (harmless below API 37)
- LocalNetwork.kt: pure detector + needsLocalNetworkPermission(baseUrl)
  (loopback + public remote exempt; RFC1918/link-local/CGNAT gated)
- ConnectViewModel: requestConnect() gates on the permission; screen
  launches the prompt; onLanPermissionResult proceeds or shows a clear error
- flake.nix: android platform 36 -> 37
- Tests: LocalNetworkTest + ConnectPermissionGateTest (SDK_INT / permission
  branching mocked, no device needed)
- Task 8 finding: assistant services (VoiceInteraction*/Recognition) and the
  remoteMessaging ChatNotificationService do NOT hit the Android 17 mic/
  while-in-use foreground hardening, so no change required.

* fix: ktlint + @Composable LocalContext hoist (was blocking CI)

- ConnectScreen: hoist LocalContext.current out of onClick lambda
  (calling a @Composable inside a non-composable lambda broke compileDebugKotlin)
- ktlint --format: multiline-expression-wrapping, function-signature,
  no-empty-first-line-in-class-body, no-unused-imports
- LocalNetwork.kt: merge consecutive KDocs (no-consecutive-comments)

flake.nix buildToolsVersion/platformToolsVersion intentionally stay 36.0.0
— they are decoupled from compileSdk; only platformVersions needs 37.

* fix: suspend mock (coEvery) + flake toolchain pinned to 37.0.0

- ConnectPermissionGateTest: getStatus() is suspend → use coEvery (was
  breaking compileDebugUnitTestKotlin)
- flake.nix: buildToolsVersion/platformToolsVersion/cmdLineToolsVersion
  pinned to 37.0.0 (verified against pinned nixpkgs 241313f — resolves to
  android-sdk-*-37.0.0 derivations; platformVersions already 37)

* fix(flake): revert cmdLineToolsVersion to 11.0 (not API-aligned)

cmdline-tools uses its own versioning (latest = 21.0, never 37.0.0) —
nixpkgs rejects 37.0.0 with 'version missing'. build-tools + platform-tools
stay 37.0.0 (verified resolve to android-sdk-*-37.0.0). flake now evals
clean against pinned nixpkgs 241313f.

* flake: track nixos-unstable + pin toolchain to latest (cmdline 22.0, platform-tools 37.0.1, build-tools 37.0.0, platform 37.1)

- inputs.nixpkgs -> nixos-unstable, flake.lock bumped to rev ffb3c9b7 (2026-08-19)
- cmdLineToolsVersion 11.0 -> 22.0 (own versioning, not API-aligned; v22_0 confirms)
- platformToolsVersion 37.0.0 -> 37.0.1
- buildToolsVersion stays 37.0.0
- platformVersions 37 -> 37.1
- verified: nix flake check --no-build -> all checks passed on hyari

* fix: unit tests — Uri stub + Build.SDK_INT final-field mock

- LocalNetwork.kt: needsLocalNetworkPermission uses java.net.URI instead of
  android.net.Uri (latter is a non-functional stub in JVM unit tests, its host
  returns null -> gate always false). Added pure requiresLocalNetworkPermission(sdkInt, baseUrl).
- ConnectViewModel: inject sdkVersion (package-private) instead of reading the
  final static Build.VERSION.SDK_INT directly; gate via requiresLocalNetworkPermission.
- ConnectPermissionGateTest: drop mockkStatic(Build.VERSION) (MockKException on
  final static field, and against repo convention); inject sdkVersion; add pure
  gate-decision matrix. Fixes 7 red tests.

* fix(test): remove illegal ':' from test function names (compile error)

* fix(test): order getString mocks so specific resources win over wildcard

* fix(flake): include android-37.0 system image so the emulator can boot

Google only publishes the android-37.0 system image (no 37.1 image exists
yet). With platformVersions=["37.1"] composeAndroidPackages found no matching
system image, leaving system-images/ empty and every AVD FATALing with
'Broken AVD system path'. Adding 37.0 pulls the published image so an AVD boots
(verified: hermes37 AVD boots Android 17 / API 37 on hyari). compileSdk/targetSdk
stay 37.

Also bump AGENTS.md SDK note 36 -> 37 (was stale).

* fix(flake): platformVersions 37.1 -> 37.0

Drop 37.1; 37.0 provides both the SDK platform for compileSdk 37 and the
published android-37.0 system image the emulator needs.

* fix(flake): remove stray tool-scratch line leaked into flake.nix (line 116)

A terminal tool's /tmp scratch rename line got appended to flake.nix, making
it invalid Nix. Removed; flake now parses + 'nix flake check' passes on hyari.

* fix(build): pin buildToolsVersion 37.0.0 to match nix SDK

AGP defaulted build-tools to 36.0.0, which isn't in the nix-managed SDK
(only 37.0.0 is provisioned). Local ./gradlew builds then tried to *install*
36.0.0 into the immutable /nix/store and failed. Pin 37.0.0 so the local
nix devShell builds. CI uses its own SDK so unaffected.

---------

Co-authored-by: Hy4ri <Hy4ri@users.noreply.github.com>
2026-08-20 14:35:42 +03:00
AlexDev_ bb4ca6bfa9 feat(chat): render LaTeX formulas natively (#939)
* feat(chat): render LaTeX formulas

Support native inline and display math with accessible Compose rendering.

* perf(chat): reuse LaTeX parsing state

Share one measurer per message and skip redundant math splitting.
2026-08-19 22:41:50 +03:00