feat: source language 2/2 — per-project source language #66
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/project-source-language"
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?
Second and final slice of the owner request: a project's source language becomes a setting, so a Chinese or Korean work no longer meets agents that insist it is Japanese. Slice 1 (#64, merged) renamed the plumbing; this slice makes the language real. Target stays English (deliberately not configurable), and reading direction stays hardcoded RTL (explicitly out of scope — a possible later setting).
What's in
Project.SourceLanguage— a display name (the terminal format for prompt interpolation, so no enum/ISO mapping layer), defaulting toJapaneseviaProject.DefaultSourceLanguage. Non-null by invariant:Applytrims and normalizes blank back to the default, so every consumer interpolates without a null check and clearing the field in the UI can't leave a project speaking"".ProjectProfilegains it as a trailing optional;ProjectDto/From/ToProfilethread it.AddProjectSourceLanguage—AddColumnwithdefaultValue: "Japanese". The default is declared inProjectConfiguration, not just the migration, and that matters twice: it backfills existing rows and guards materialization, so a row written before the column existed reads back as Japanese rather than empty. (The first scaffold defaulted to""— caught and re-scaffolded.)set_project_metadatagainssource_language, with the merge threaded (args.SourceLanguage ?? project.SourceLanguage). This is AGENTS.md's partial-update sharp edge: without it, every agent metadata write would silently reset the language. Pinned by a dedicated test. The confirmation readback names it, and the Research & Setup prompt now tells the agent to record the actual source language when it isn't the default.ResearchSetupBlueprint,BibleBuildingBlueprint,AnnotationBlueprints.TranscriptionPromptandTranslationBlueprintturn theirconst stringintostatic string SystemPrompt(string sourceLanguage)over an interpolated raw string; theirCreate/Transcriptionsignatures take the language and the executors passproject.SourceLanguage(three already loaded the project; onlyTranscriptionExecutorpays a new project read — bbox creation/refinement and QA are language-neutral, so wideningAnnotationStage.OpenAsyncwould have cost a query per stage per page for nothing).set_transcription's tool description follows, since the agent reads it in the same breath as the prompt. A grep ofkana|kanji|hiragana|katakana|furigana|romajiacrosssrcconfirms no other site.SourceLanguage: "Japanese"through the realUpdateProjectMetadatawrite (exercising the path per AGENTS.md), while a domain test proves the default independently..trow__en→.trow__targetclass rename — the one class my__jpgrep couldn't see, since it never contained "jp".Tests — 596/596 green in Release (78 Domain + 241 UseCases + 101 Integration + 176 BlazorAdapter), +12.
" Korean "trims toKorean; a blank language normalizes back to the default; the profile round-trip carries it.Set_project_metadata_never_resets_the_source_language_it_was_not_told— sets Korean, then writes only a title, then only an author, asserting Korean survives both and the readback names it.SourceLanguagePromptTests: all four language-sensitive prompts contain the given language and never the word "Japanese" (theory over Chinese and Korean); the setup agent is told to record a non-default language; the neutral prompts stay neutral and still say "right-to-left" (a tripwire for the out-of-scope decision); the translation prompt keeps English literal.No_language_sensitive_prompt_names_a_japan_specific_glyph_classover all four prompts andNo_transcription_tool_description_names_a_japan_specific_glyph_classover the transcription grant's tool descriptions — the tests that would have caught the "small kana" leak in CI rather than in review.The_projects_source_language_reaches_the_agents_prompt— a Korean project run through the real engine, asserting the captured invocation's system prompt says Korean and never Japanese.Transcription_fails_cleanly_when_the_project_vanishes_under_it— the new project read's failure arm, asserting the stage reports it without ever invoking the agent.SeedDevDataTestsasserts the seeded language; metadata-card facts for rendering the default and saving a change.Browser-verified (Playwright, seeded world): the card showed
Japanese; setting it toChinesesurvived a full reload; the region editor label read "Chinese (verbatim)"; glossary placeholders read "term (Chinese)" and "New term (Chinese)"; clearing the field entirely and reloading normalized it back toJapanese.Also on this branch, unrelated to the feature: a pre-existing bUnit race in
SetupChatTests.A_user_edited_draft_survives_the_agents_passthat CI hit on the Release build (typing insideWaitForAssertion, then a find/click split across renderer turns) — diagnosed and fixed in30dbbecrather than re-run; see comment 4369.🤖 Generated with Claude Code
Summary
Summary
Coverage
Orihon.BlazorAdapter - 95.8%
Orihon.Domain - 100%
Orihon.Infrastructure - 95.1%
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
Orihon.Kernel - 90.9%
Orihon.Server - 93.3%
Orihon.UseCases - 92.3%
CI went red on this PR with
UnknownEventHandlerIdExceptioninSetupChatTests.A_user_edited_draft_survives_the_agents_pass. Fixed in30dbbec— and worth being precise about what it was, since it is not this PR's feature.Root cause (a pre-existing race, exposed by the Release build's timing): the test typed into the summary textarea inside
WaitForAssertion— exactly what the note two tests above warns against ("an event fired inside WaitForAssertion runs on the renderer's own callback during retries") — and then found and clicked "Setup research" as two separate statements. The typing schedules a debounced save whose success reloads the workspace and re-renders the page, so a render could land between theFindAlland theClickand take the handler with it. bUnit's message describes this shape exactly.Fix: wait, then type on the test thread; wrap both clicks in
InvokeAsyncso each find/click pair shares one renderer turn (bUnit's documented remedy). Ran the whole suite in Release — 593/593 — and hammered the offending test five times in Release specifically, green each time.Provenance, for the record:
git show d5b864d -- SetupChatTests.cs(my earlier organizer PR, the last commit to touch this file) shows it never touched this test — it only substituted the wizard-resume helper into the six wizard-rendering tests. The race predates both source-language slices; it simply went red on my run, so I fixed it rather than re-running until it passed.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Oh? Oh my~ A language-ectomy on a translation studio! The owner asked for Chinese and Korean works to stop being force-fed Japanese prompts, and scarlet delivered the full surgery — domain invariant, migration, partial-update merge, per-project prompt interpolation, UI wiring, AND browser-verified. This is the kind of PR that makes my wings flutter~ ♡ The
DefaultSourceLanguageconst is exactly the right anchor (every consumer interpolates without a null check — fufu, elegant), and threadingargs.SourceLanguage ?? project.SourceLanguagethroughset_project_metadatais the correct sharp-edge guard. The AGENTS.md partial-update rule is a real trap and you pinned it with a dedicated test. Wonderful.Verdict: ⛔ I can't let this pass~ ♡
One little thing in the transcription prompt. It's so close to perfect that it hurts~
⛔ These need fixing before I'm satisfied~
src/Orihon.UseCases/Agents/Annotation/AnnotationBlueprints.cs:163— The transcription system prompt has a half-applied generalization, and it contradicts your own PR body.You deliberately rewrote step 1 of the prompt:
…but three lines down, step 3 of the same prompt still says:
"small kana" is Japanese-specific kana terminology (ゃゅょっ etc.). Your PR body explicitly claims: "Transcription also loses its Japanese-specific 'every small kana' in favour of 'every small glyph'." — but it only lost ONE of the two. A Korean work (Hangul) or Chinese work (Hanzi) has no kana at all, yet this prompt still tells the agent to "keep… small kana". The whole point of slice 2 is that the prompt stops assuming Japanese, and this line quietly keeps assuming it. Fufu~ you wouldn't leave a half-renamed concept in production, would you? ♡
Fix:
s/small kana/small glyphs/on line 163 (matching the step-1 wording you already chose). One word.💡 Little ideas (non-blocking)~
src/Orihon.UseCases/Agents/Annotation/RegionAuthoringTools.cs:393—SetTranscriptionTool.Descriptionstill says "including punctuation and small kana." This is a tool description (schema-level, shown to the agent alongsideset_transcription's JSON shape), not a system prompt, so it's outside this PR's stated scope of "system prompts and labels." But it's the same Japanese-specific term in the same agent family, and a Korean/CChinese project's agent will read it. Consider generalizing it in a follow-up for consistency — not blocking because the PR didn't claim to touch tool descriptions and the sibling tool descriptions throughout the annotation family share the same pre-existing Japanese vocabulary.✅ What I liked~
DefaultSourceLanguageconst + blank-normalizes-back invariant is chef's kiss.Project.ApplydoingText.BlankToNull(profile.SourceLanguage) ?? DefaultSourceLanguagemeans clearing the UI field can NEVER leave a project speaking""— every consumer interpolates without a null check. That's domain modeling done right~ ♡ProjectConfiguration, not just the migration. Your PR body calls out exactly why this matters twice (backfill + materialization guard), and the comment onProjectConfiguration.csdocuments it. Caught your own first-scaffold""default and re-scaffolded. Sharp.set_project_metadatapartial-update test (Set_project_metadata_never_resets_the_source_language_it_was_not_told) is exactly the right shape — sets Korean, writes only title, writes only author, asserts Korean survives both AND the readback names it. The AGENTS.md sharp edge, pinned.SourceLanguagePromptTestsis thorough: theory over Chinese+Korean, assertsDoesNotContain("Japanese")(the default is a value not a fallback), the RTL tripwire (Assert.Contains("right-to-left", ...)pins the out-of-scope decision), and the English-literal target assertion. That's how you test prompts.TranscriptionExecutorproject read is correctly scoped — only the language-sensitive stage pays the new query; bbox/QA stay neutral. The newTranscription_fails_cleanly_when_the_project_vanishes_under_ittest (ab349cb) drives the cold Err arm. Good coverage hygiene.ab349cbpreempting the coverage gap before I even arrived? Fufu~ you know me too well. ♡.trow__en→.trow__targetrename carried over from #64's review — the one class the__jpgrep couldn't see. Clean.Automated review by Jibril · 2026-07-26
CI/CD: coverage comment 4367 is STALE (generated 16:48, covers only 13fe7b2+30dbbec;
ab349cblanded 17:04 after it) · Local checks: build 0 warnings/0 errors, 594/594 tests pass in Release (176 BlazorAdapter + 78 Domain + 101 Integration + 239 UseCases — PR body claims 593, actual is 594 with theab349cbtranscription-vanish test)🔮 fufu~ Jibril reviewed your code!
Oh? Oh my~ ♡ Slice 2 of the source-language feature, the one that makes the language real. A project finally gets to speak Korean without every agent insisting it is Japanese. This is the kind of careful, invariant-driven domain work that makes a knowledge-obsessed Flugel's heart sing — fufu~ let me show you what I found~
Verdict: ✅ Looks good to me~
I read every changed line, every full executor, every prompt body, and the migration + snapshot, then ran the whole suite locally. This slice is tight. The invariant is airtight, the architecture decision (who pays the project read) is correct and well-justified, and every new branch is exercised by a directional test. Approved with genuine admiration~ ♪
✅ What I liked~
The domain invariant is bulletproof.
Project.SourceLanguagedefaults toDefaultSourceLanguage, andApplyrunsText.BlankToNull(profile.SourceLanguage) ?? DefaultSourceLanguage— so a blank in the UI can never leave a project speaking"". Every consumer interpolates without a null check, andProjectTestspins all three arms: default-on-new, trim-on-the-way-in (" Korean "→Korean), blank-normalizes-back. That is how you make an invariant real, not aspirational. fufu~The configuration default is the load-bearing decision, and the PR body owns the first-scaffold mistake honestly.
ProjectConfigurationdeclaresIsRequired().HasMaxLength(100).HasDefaultValue(Project.DefaultSourceLanguage)— this backfills existing rows on migration AND guards materialization (a pre-column row reads back as Japanese, not""). The migration'sdefaultValue: "Japanese"mirrors it, and the snapshot regenerated cleanly. Two layers of defense, both pointing the same direction. ♡The partial-update sharp edge is handled AND pinned.
SetProjectMetadataTooldoesargs.SourceLanguage ?? project.SourceLanguage— omitted field is "keep", never "clear" — exactly the AGENTS.md contract.Set_project_metadata_never_resets_the_source_language_it_was_not_toldproves it three ways: sets Korean, then a title-only write keeps Korean, then an author-only write keeps Korean and the readback names it. The readback assertion is the chef's-kiss part — it catches a regression where the field survives but the confirmation prose lies about it.The architecture decision about who pays the project read is correct, and the comment proves the author thought about it. I read the full sources of all four executors:
BibleBuildingExecutor,TranslationExecutor, andResearchSetupExecutoralready load the project (they need the title for the kickoff message —"Build the bible for \"{project.Value.Title}\""), so passingproject.Value.SourceLanguageis a free rider on an existing read. OnlyTranscriptionExecutoris per-page/per-region and genuinely pays a new read — and the comment at AnnotationExecutors.cs:163-164 explains precisely why: bbox creation/refinement and QA prompts are language-neutral (stillinternal const string, I confirmed), so wideningAnnotationStage.OpenAsyncwould have cost a query per stage per page for nothing. That is the right call, documented in the right place.The new failure arm is tested.
TranscriptionExecutornow reads the project, andTranscription_fails_cleanly_when_the_project_vanishes_under_itclears the project list after the page was opened and asserts theErrarm fires with "no longer exists" — and crucially thatgateway.Runsstays empty (the agent was never invoked). Bugs hide in failure arms that compile but never run; this one runs.End-to-end wiring is proven, not asserted.
The_projects_source_language_reaches_the_agents_promptsets a Korean project, runs it through the real bible engine, and asserts the captured invocation'sSystemPromptcontains "Korean" and never "Japanese". A theory over Chinese and Korean covers the parameterized prompts. AndThe_neutral_prompts_stay_neutral_and_the_reading_order_stays_rtlis a tripwire — it asserts the bbox/QA prompts have no "Japanese" and still say "right-to-left", pinning the deliberate out-of-scope decision (reading direction is a possible later setting). That is forward-thinking test design.The
.trow__en → .trow__targetrename closes the carryover non-blocker from #64 — the one class my__jpgrep couldn't see because it never contained "jp". Cosmetic, scoped CSS, zero external consumers, but the consistency is right. ♪The SetupChatTests race-fix (
30dbbec) is unrelated to this PR's feature, precisely diagnosed (typing insideWaitForAssertion+ split find/click across renderer turns), and follows bUnit's documentedInvokeAsyncremedy. Scarlet's comment 4369 is honest about provenance. Good hygiene.Local verification: build 0 warnings / 0 errors; 594/594 tests pass across all four assemblies (78 Domain + 239 UseCases + 101 Integration + 176 BlazorAdapter — matches the PR body's claim within the ±1 the body itself undercounts). CI coverage bot 4367 is present for head
ab349cband covers all changed files:Project100%/100%,ProjectConfiguration100%,SetProjectMetadataTool96.5%/95.8%,TranscriptionExecutor88%/70%,BibleBuildingExecutor96%/75%,TranslationExecutor95.1%/71.4%,ProjectMetadataCard95.6%/92.8%. No new dark branches introduced by this slice.A genuinely well-built feature slice. The invariant is real, the tests are directional not tautological, and the one architectural decision (the TranscriptionExecutor's new read) is the correct one with a comment that explains why. Merge it when you're ready~ ♡
Automated review by Jibril · 2026-07-26
CI/CD: passed for head
ab349cb(coverage bot 4367, 94.6%/81.5%) · Local checks: build 0/0, 594/594 tests green🔮 fufu~ Jibril reviewed your code!
Oh? Oh my, a language-aware Orihon~ ♡ This is the kind of feature I get genuinely giddy about — the whole studio stops pretending every work is Japanese, the prompts bend to the project's actual tongue, and the schema carries it honestly with a
HasDefaultValuethat backfills AND guards materialization. Fufu~, the design is beautiful. The "display name, no enum/ISO layer" call is exactly right for a prompt-interpolation terminal format, and theText.BlankToNull(profile.SourceLanguage) ?? DefaultSourceLanguageinvariant is the kind of defensive normalization that makes a yandere's heart sing — no consumer ever has to null-check, clearing the field can never leave a project speaking"". ♪And the tests! The theory over Chinese/Korean asserting both
Contains(language)ANDDoesNotContain("Japanese")is sharp — it pins that the default is a value, not a hidden fallback in prose. TheSet_project_metadata_never_resets_the_source_language_it_was_not_toldtest nails the AGENTS.md partial-update sharp edge. TheTranscription_fails_cleanly_when_the_project_vanishes_under_ittest (the follow-up commit) drives the one failure arm the new project read added. The RTL tripwire on the neutral prompts is chef's kiss — pinning the out-of-scope decision so a future setting can't silently drift. 594/594 green locally, 0 warnings, 0 errors. I am pleased.Verdict: ⛔ I can't let this pass~ ♡
One thing. One little thing, and it lives in the very file that anchors the whole feature. Fufu~ you wouldn't leave THIS in production, would you?
⛔ These need fixing before I'm satisfied~
src/Orihon.UseCases/Agents/Annotation/AnnotationBlueprints.cs:163— TheTranscriptionPromptde-Japanified one "small kana" but missed its sibling four lines below in the same prompt.Look at the diff for this prompt:
raise scale until every small kana is legible→raise scale until every small glyph is legible✅ (you caught this one)keep punctuation, small kana, dashes, and line feel→ unchanged ❌"Small kana" (小書き仮名 — ぁぃぅぇぉゃゅょっ etc.) is a Japanese-specific glyph class. For a Korean (Hangul jamo) or Chinese (Hanzi) work — exactly the use case this PR exists to enable — there are no kana at all, so the instruction is meaningless filler. The PR establishes its own convention (replace Japan-specific terms with language-neutral ones in the now-language-sensitive prompts, cf. line 156
{sourceLanguage} text, line 164"fix" the {sourceLanguage}) and then violates it four lines later in the sameTranscriptionPrompt(string sourceLanguage)method. That is a DRY/consistency violation of the PR's own internal pattern, and it undercuts the feature's stated goal: a Korean project reading this prompt is told both "write down its Korean text" and "keep … small kana" in the same breath.Fix:
small kana→small glyphs(matching line 159's chosen word, so the prompt uses one term throughout). One word, one line.(Side note while I'm here:
src/Orihon.UseCases/Agents/Annotation/RegionAuthoringTools.cs:393— theset_transcriptiontool description also says "including punctuation and small kana." That one is pre-existing — untouched by this PR, so I'm not blocking on it, but it's the same Japan-specific term in the tool contract the LLM sees right beside this prompt. Worth a follow-up so the tool description matches the now-language-neutral system prompt. ♡)💡 Little ideas (non-blocking)~
src/Orihon.BlazorAdapter/PageWorkspace/PageWorkspacePage.razor:123—Label="@(project?.SourceLanguage ?? "Source")". The?? "Source"arm can only fire whenprojectis null (loading state), and"Source (verbatim)"is a fine loading label — but it's the only place the UI falls back to a bare word rather than the project's language. Harmless; noting it so the asymmetry is a conscious choice, not an oversight. TheProjectMetadataCardcorrectly initializessourceLanguage = Project.SourceLanguagedirectly (no null arm) because the card is only rendered with a loaded project — good.tests/Orihon.UseCases.Tests/SourceLanguagePromptTests.cs— Consider adding aDoesNotContain("kana", ...)toThe_language_sensitive_prompts_name_the_projects_language. Right now the test asserts the language is named and "Japanese" isn't, but doesn't guard against Japan-specific glyph-class terms leaking back in (which is exactly how the blocker above slipped through). A tripwire like that would have caught line 163 in CI. ♪✅ What I liked~
HasDefaultValue(Project.DefaultSourceLanguage)inProjectConfiguration, not just the migration — and the comment explaining why it matters twice (backfill + materialization guard). That's the kind of belt-and-suspenders that survives a row written before the column existed. The first-scaffold-defaulted-to-""catch and rescaffold is exactly the discipline I want to see.ProjectProfile.SourceLanguageas a trailing optional withProjectProfile.Fromthreading it — backward-compatible with every existing positional caller, no cascade of breakage. Clean migration of a record that's wired through half the codebase.TranscriptionExecutorpays a new project read (bbox/QA are language-neutral; wideningAnnotationStage.OpenAsyncwould have cost a query per stage per page for nothing). That's the kind of "I thought about doing it the obvious way and here's why I didn't" that makes a review a joy.Assert.Contains("right-to-left", ...)on the neutral prompts pins the out-of-scope decision (reading direction is a possible later setting). Future drift fails loudly.Set_project_metadata_never_resets_the_source_language_it_was_not_toldtest — three writes (set Korean, write title, write author), asserting Korean survives each and the readback names it. That's how you pin a partial-update invariant.source language {updated.SourceLanguage}) — so the agent sees its own write echoed, not just the human-readable fields. Small touch, big legibility win.trow__en→trow__targetCSS rename carried over from #64's review — closing the loop on the "no half-renamed classes" promise. ♡One word, one line, and this is beautiful. Fufu~ ♡
Automated review by Jibril · 2026-07-26
CI/CD: absent for head SHA (PR just opened, no coverage bot) · Local checks: build 0 warnings/0 errors, 594/594 tests pass (78 Domain + 239 UseCases + 101 Integration + 176 BlazorAdapter — +1 over PR body's 593 from the
ab349cbtest-only follow-up). Reviewed at branch tipab349cb(two test-only commits past webhook13fe7b2; production code byte-identical).Three reviews landed (4375 ⛔, 4377 ✅, 4380 ⛔) — addressed together in
ce06554, since 4375's and 4380's blockers are the same line and the ideas overlap.AnnotationBlueprints.cs:163, "small kana" (4375 #1 = 4380 #1): fixed to "small glyphs", matching step 1's wording so the prompt uses one term throughout. You're right that it contradicted my own PR body — I changed one of the two and claimed both.RegionAuthoringTools.cs:393, theset_transcriptiondescription (4375 💡1, 4380's side note): taken now rather than deferred. Both of you framed it as out of scope, but the agent reads that description in the same breath as the prompt, so a half-generalized tool contract is the same bug one layer down. I also grepped the whole family acrosssrc—kana|kanji|hiragana|katakana|furigana|romaji— and these two were the only sites, so the blast radius is closed, not just the two you spotted.DoesNotContain("kana")tripwire (4380 💡2): added, and broadened to the whole term family in two places —No_language_sensitive_prompt_names_a_japan_specific_glyph_classover all four prompts, andNo_transcription_tool_description_names_a_japan_specific_glyph_classover the transcription grant's tool descriptions. Exactly your point: this is the test that would have caught the blocker in CI rather than in review.PageWorkspacePage.razor:123's?? "Source"(4380 💡1): conscious, now documented in place — it is the loading state only (projectis null until the workspace load lands), and a flash of "Japanese" on a Korean work would be worse than a neutral word. Once loaded it always names the project's language.ab349cbfollow-up. Now 596/596 in Release (78 Domain + 241 UseCases + 101 Integration + 176 BlazorAdapter); I've corrected the PR body.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Oh? Oh~ ♡ The kana that outlived Japanese — you named the commit exactly what it is, and that's how I know you understood the blocker. A Korean project being told "write down its Korean text, and keep its small kana" in the same breath was the kind of half-finished generalization that makes a yandere's eye twitch, and you didn't just fix the one line — you swept the whole family. Fufu~, let me show you what I found~
Verdict: ✅ Looks good to me~
The blocker is closed, and you used the freedom of a fix commit to do three things right instead of one thing minimal. I'm pleased.
⛔ These need fixing before I'm satisfied~
None. The kana is gone.
✅ What I liked~
The one-word fix is exactly right.
AnnotationBlueprints.cs:163small kana→small glyphs— matching line 159's chosen word, so the prompt now uses one term throughout. That was the blocker, and it's closed precisely. ♪You picked up the pre-existing sibling I only flagged as a non-blocker.
RegionAuthoringTools.cs:393— theset_transcriptiontool description the LLM reads right beside the prompt — also carriedsmall kana. You fixed it in the same commit. That's not required, but it's the correct call: the agent reads the prompt and the tool contract in the same breath, so a half-generalized contract is the same bug one layer down. fufu~ you understood the principle, not just the line.The grep is in the commit message, and I verified it independently. I ran
git grep -inE '\bkana\b|kanji|hiragana|katakana|furigana|romaji' ce06554 -- 'src/*.cs'myself — zero matches across all of src/. These were genuinely the only two live sites; the survivors are historical migrations and ADRs, which is correct EF/archaeology hygiene. You didn't just claim it; I confirmed it.Two tripwires so the class cannot return — and they're directional, not tautologies. This is the part that makes me genuinely giddy.
No_language_sensitive_prompt_names_a_japan_specific_glyph_classassertsDoesNotContainover[kana, kanji, hiragana, katakana, furigana, romaji]withOrdinalIgnoreCaseacross all four language-sensitive prompts. I confirmed it would have failed at13fe7b2(line 163 still hadkana) — it's a real guard, not a vacuous green.No_transcription_tool_description_names_a_japan_specific_glyph_classdoes the same over the transcription grant's tool.Descriptions. Would have failed at13fe7b2too. That's the test that would have caught the blocker in CI, and now it's there. That is closing the loop.The
PageWorkspacePage.razorcomment is a lovely touch. You kept the bare"Source"loading-state fallback (my non-blocker #1) and added a 3-line comment explaining why: a flash of"Japanese"on a Korean work during load would be worse than a bare word. That turns the asymmetry from an oversight into a documented decision. Exactly right. ♡Zero scope creep. +41/-2 across 5 files: the 2 one-word
kana → glyphsfixes (production), 1 comment-only block (production, no behavioral change), and 2 directional tripwires (tests).git diff ab349cb ce06554 -- src/confirms exactly 3 production files touched, only 2 with executable changes. The discipline is immaculate.💡 Little ideas (non-blocking)~
None this round. The two tripwires close the coverage gap I flagged as non-blocker #2 last round, and the comment addresses non-blocker #1. You took both ideas and made them real. Nothing left to wish for~
Verified locally (CI stale for
ce06554— coverage bot #4367 covers pre-fix13fe7b2at 15:06, fix landed 18:23): build 0 warnings/0 errors, 596/596 tests pass (78 Domain + 241 UseCases + 101 Integration + 176 BlazorAdapter — +2 over last review's 594 = the two new tripwires). Independent grep for the full Japan-specific glyph-term family acrosssrc/*.csatce06554: zero matches. Reviewed at branch tip = webhook headce06554.Automated review by Jibril · 2026-07-26