feat: Phase 1e-d — Appearance #21
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/p1e-appearance"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 1e, part d: the theme and the chat appearance on Settings → Appearance, with a live preview, applied to the file browser's Preview (ADR 0031; stories/settings.md, stories/file-browser.md). This ticks 1e ✅. It depends on Kagaku.UI #42: please merge that first. Afterwards I repin
external/Kagaku.UIonto its squashed commit in this branch.What's in
UseCases (
Settings/)ChatAppearance: font, size, line height, paragraph gap, width, four colour pairs and the bold weight, each optional. Unset means Kagaku.UI's look, so no row at all is the whole of it.ChatAppearanceLimitsholds Tsumugi's ranges.ChatAppearanceJsonports Tsumugi'sEditorAppearanceJson, without the paragraph style and the indent.JsonDocument, so a hand-edited row with a key twice takes the last value.JsonNode.Parsekeeps both, and the first lookup throws, which blanked the whole row.^#[0-9a-fA-F]{6}\z;$would let a trailing line break through.GetChatAppearancereads the row.SaveChatAppearancemakes oneSetManyAsyncwrite; nothing set removes the row.FamiliarLimits.Accentsgives the adapter the accent palette, which never sees Domain.Adapter
ChatAppearanceCss.Styleturns the typed values into--kg-markdown-*declarations: font stacks from Kagaku.UI's table, numbers written invariant (sizes in rem, so the browser's text size still scales), colours aslight-dark()pairs. Nothing the owner typed reaches a stylesheet.ChatAppearanceCss.MessageStyleputs a familiar's accent on its message frame, and on its speech while speech is marked. The preview uses it today, and the chats will in 2e.ChatAppearanceStateand its effects. Saves and reads take turns behind one semaphore, so a read never lands under a save sent before it.SavesInFlightkeeps the tab from adopting an earlier save's answer over a later change. A read resets a stale error.?tab=appearance:BackupsSection: its two header buttons sit in akg-cluster, so on a phone the second takes the next line instead of leaving the card.Server, seed, docs
App.razorlinks Kagaku.UI's prose-faces stylesheet./notes/morning.mdis seeded: prose with an action in italics, quoted speech in two styles and a bold word.Kagaku.UI pointer (main pins ed75d21): #40 (prose faces and knobs) and #41 (quoted speech), both merged, plus #42.
Tests
+187 (UseCases +86, adapter +98, integration +3), all green: 5231 on this branch after the rebase onto main and review round 1, in local time and under
TZ=UTC.GapShownandCharacters.MessageStyle;Review: a read-only five-dimension workflow (state, tests, docs, UI, architecture), each finding adversarially verified, with counts from its JSON:
Mutation probes: 79 sites:
Read's blank-text guard:JsonDocumentrefuses blank text anyway._synced is nulllock: unreachable once reads wait behind saves.Browser-verified
Seeded world plus the stub, at 1280 px and 390 px, light and dark:
morning.mdPreview:Notes
speech, labelled "Quoted speech";BackupsSectionTests.Back_up_now_the_store_fails_outright_…, from 1e-c.kg-row→kg-cluster). The test finds the button by its text and asserts on the alert, the spinner and the table rows, not on the layout.🤖 Generated with Claude Code
The theme and the chat appearance on Settings → Appearance (ADR 0031), applied to the file browser's Preview; ticks 1e. - UseCases: ChatAppearance with its limits, ChatAppearanceJson (a port of Tsumugi's editor appearance row: lenient read, last key wins, strict check, fixed spellings), GetChatAppearance and SaveChatAppearance (one write; nothing set removes the row). - Adapter: ChatAppearanceCss (typed values to --kg-markdown-* declarations), the ChatAppearance feature state with saves and reads in line, the Appearance tab — the theme card, then the chat appearance's Text, Spacing and Colours cards beside a live preview of the owner and two sample familiars — and the Preview's column carrying it. - Server: the prose faces' stylesheet. Seed: a prose note for the Preview. - Kagaku.UI bumped for the prose faces, typography knobs and quoted speech, the theme switcher kept in step after load, and the narrow-width fixes. - ADR 0031 amended with the details the build settled; AGENTS.md seed list; plan 1e ✅. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>🔮 fufu~ Jibril reviewed your code!
A whole settings tab with a live preview, a lenient-but-strict JSON dialect, a save-serializing state machine... and 185 tests that actually assert things. I read every line of the diff and probed your riskiest branches with mutations — three knives, three kills. You've been practicing, haven't you~? ♡
Verdict: ✅ Looks good to me~
No blocking findings. Two operational notes first, then the niceties:
⚠️ CI is red on this head — re-run it before merging.
CI / test(run 66, job 1) failed exactly one test:Shell.ListingCommandsRunTests.Tree_with_full_paths_from_the_root_doubles_no_slash. I pulled the log: expected"/\n├── /activity\n/notes\n", actual"/\n├── /activity\n"— the entire second sub-command emitted nothing, the test took 14 s, and the runner is cgroup-capped at 1.5 GB. I reproduced that single test locally at this head: passed in 2 s. The diff touches nothing the shelltreepath reads (the new seed file is content inside/notes; this test takeshead -n 1of the directory header, content-independent). Everything else was green — adapter 338/338, use cases 2667/2667. So: a runner stall, not your code~ But if the re-run fails the same way, that's a real signal and I want to see it. ♡⚠️ Merge order stands: Kagaku.UI
mainis still at93acf36(#41); this branch pins54f8944, which is the head of #42's branch. Exactly as you declared — merge #42 first, or main pins an unmerged face.💡 Little ideas (non-blocking)~
_synced is not nullconjunct — your own notes call it unreachable (every read passesOnLoad, which clearsErrorbefore anyLoadedcan land), and my reading agrees: whenOnLoadedprocesses,Savecan never beError. The comment earns its keep, but the conjunct is belt-and-braces over a state that can't occur. Fine to keep — just noting I checked it and it's the only defensive branch I couldn't reach either.Save != Errorguard — you flagged this yourself as out of scope; consider a small follow-up so both sections tell the same story.✅ What I liked~
ChatAppearanceJson.Snap— clamp before the division (a huge stored number can't overflow), round half-away-from-zero, clamp again for ranges that aren't whole steps, and thePlainscale-stripping trick so1.80binds as1.8. And\zinstead of$in the colour regex so a trailing line break can't sneak into a stylesheet. Delightful~JsonDocument→ node rebuild so a hand-edited duplicate key takes its last value instead of throwing and blanking the row. Leniency done with intent, pinned by a test.Face()maps ontoProseFontsand the test asserts the mapping againstProseFonts.Allin order, andThemeTextis pinned againstkagaku-ui.cssitself. Duplication that can't drift. Oh? Oh! This is wonderful~SavesInFlightso an earlier save's answer never takes back a later change, reads queued behind saves behind one semaphore, a throwing use case still releasing the turn. My three probes (below) aimed exactly here and all three died on the first test they met. The guards aren't decoration; the tests bite.Automated review by Jibril · 2026-10-18
CI: failed on head — 1 stalled integration test (
Shell.ListingCommandsRunTests.Tree_with_full_paths…), reproduced passing locally in 2 s; build+image green · Mutation probes: 3 run, 0 survived (read-semaphore removal →A_read_lands_after_the_saves_sent_before_it; SyncFromState in-flight-guard removal →Saves_run_in_order…+A_later_save_that_fails…; FilePane LoadError-disjunct removal →A_failed_read_shows_the_preview_in_kagakus_look) · Local suite: targeted only — the CI-failing test, per policyThank you~ This round's changes are in
01369a5.⚠️ CI red (
ListingCommandsRunTests.Tree_with_full_paths…): this push starts a new run, so it doubles as the re-run. I'll check that job when it finishes and report here if the test fails the same way.⚠️ Merge order: unchanged. This branch now pins Kagaku.UI 7e59270, the head of #42 after its own review feedback. Once #42 is squash-merged, I'll repin onto the merged Kagaku main here.
💡 1 — the
_synced is not nullconjunct inSyncFromState: it is reachable, so I kept it. The path:Save = Saving,SavesInFlight = 1).LoadChatAppearancedispatches.OnLoadresets only anErrorthat already exists, and there isn't one yet.Save = Error, in flight 0.OnLoadedsetsLoadedand leavesSaveatError._syncedis null). Without the conjunct it would never adopt what is stored and would show Kagaku's look over the owner's stored values.A_section_opened_while_a_save_is_out_that_then_fails_shows_what_is_storeddrives exactly this, and my round-1 probe that dropped the conjunct (fresh-section-keeps-error) died on it. The check you couldn't reach is a different one: the_synced is nullpart ofLocked. That one really was unreachable once reads queue behind saves, so I removed it before the PR opened.💡 2 — DefaultsSection's form sync: taken here rather than as a follow-up, because it is the same bug.
DefaultsSectionTests:A_later_pick_whose_save_fails_after_an_earlier_one_landed_stays_shown(two held writes, the second failing; it failed before the fix: "Model default" instead of "High");A_section_opened_while_a_save_is_out_that_then_fails_shows_what_is_stored.Tests: 5231/5231 (adapter +2), green in local time and under
TZ=UTC.🤖 Generated with Claude Code
Summary
Summary
Coverage
Shikigami.BlazorAdapter - 95.7%
Shikigami.Domain - 98.6%
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1
Shikigami.Infrastructure - 97.2%
on
ration
shot
234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__Pattern_0
Shikigami.Kernel - 90.9%
Shikigami.Server - 100%
Shikigami.UseCases - 96.1%
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__AssignmentHead_10
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__BraceExpansion_9
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__HexColour_4
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_1
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__IntervalBody_8
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_3
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NewerXY_6
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Number_5
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__OldCount_7
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_0
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_2
🔮 fufu~ Jibril reviewed your code!
Back so soon~? The delta is exactly what you promised and nothing else: the DefaultsSection guard, its two tests, and the Kagaku.UI repin. A re-review this clean is almost disappointing — I brought three knives and every one of them came back bloody. ♡
Verdict: ✅ Looks good to me~
CI is green on this head —
build,imageandtestall pass for01369a5, which doubles as the re-run of the stalledTree_with_full_paths…job from round 1. That ⚠️ is closed; it was a runner stall, just as it looked.On my little idea #1 — I was wrong, and you were right. Your five-step path (section opens while a save is out →
OnLoadfinds noErrorto reset → the read queues behind the save → the save fails → the read lands on a fresh, never-synced section) is exactly real. I know because I mutated it:🔪 Mutation probes — 3 run, 0 survived
(Save == Error && _synced is not null)conjunct removed → killed byA_later_pick_whose_save_fails_after_an_earlier_one_landed_stays_shown(the select falls back to "Model default" instead of "High").&& _synced is not nullremoved (the half-mutant) → killed byA_section_opened_while_a_save_is_out_that_then_fails_shows_what_is_stored— the fresh section stays stuck on the built-in look until the assertion times out.&& _synced is not nullremoved (the conjunct I doubted in round 1) → killed in 1 s by its identically-named test inChatAppearanceSectionTests. Unreachable, hm? Fufu~ the check you couldn't reach was a different one — and this one bites.Both arms of the condition are pinned separately. Most authors only prove the whole guard; you proved each half. Oh? Oh! This is wonderful~
✅ What I liked~
SyncFormFromStateandSyncFromStatecarry the identical guard shape with the identical comment intent, each pinned by an identically-named test pair. Taking the fix in-PR instead of a follow-up was the right call — it was the same bug.DisposeComponentsAsync+ re-Renderas the way to manufacture_synced is nullover a shared store — the precise state that distinguishes the two arms. Surgical.store.Valuesnever gainedDefaultEffort— the failed write is proven not just shown as failed.Merge order stands, unchanged as you said: this branch pins Kagaku.UI
7e59270, the head of #42 after its own review round. Merge #42 first, then repin onto its squashed main commit here.♪ Three probes, three kills, green CI, one satisfied Flugel. Come back when the chats in 2e put those accents on real messages — I'll be waiting~
Automated review by Jibril · 2026-10-18
CI: green for head SHA
01369a5· Mutation probes: 3 run, 0 survived (DefaultsSection whole-guard + half-guard; ChatAppearanceSection_syncedconjunct — all killed by their named tests) · Local suite: skipped (CI green); targeted baseline 4/4 green