feat: Phase 1d-b — The special-file forms #10
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/p1d-b-special-forms"
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 half of Phase 1d (
docs/PROJECT_PLAN.md), which this PR ticks ✅: the Form tab for special files, with the memory form (budgets as usage bars) and the skill view, each problem under its field (ADR 0015, 0018;docs/stories/file-browser.md). The plan puts the routine form with its Next firings in 3a — Routines, with the scheduler. Until then a routine opens on Text with its problems, as the ADR 0015 amendment records.What's in
UseCases
HeaderWriter(Files/Special): a form save rewrites only the header, in canonical order, and keeps the body byte for byte. The body is spliced from the raw text after the closing---line, becauseFrontMatter.Bodyis normalised.Tea: greenis quoted and reads back as written.\U0001F375). Each one stands in as an unused private-use character during emitting, so the owner and the familiar read the emoji. A literal\U…the owner typed stays text.SpecialForms(Files/Browser, string-based so the adapter needs no Domain types):ReadMemoryshows the header as written, even values the validator refuses.WriteMemoryrewrites only the field the owner changed. The other field, and any other key, stays as written, even when it is refused.Usecounts the draft for the bars exactly asBudgetsdoes: pinned means its body, otherwise its index line, and an invalid memory counts for nothing.ReadSkillreads the skill for the view.Startergives a new memory orSKILL.mdthe smallest header its folder takes:Budgets.Measure: public usage figures, counted as the overflow check counts.FileViewgainsMemory(the other memories' usage and the familiar's own limits) andOwnerAuthoredfor skills.Files.CreatedByrecords a file's first author on its row. A move and the trash keep it, and pruning or Purge history cannot drop it. The old reading (the oldest stored revision) flipped "Written by you" once history was pruned or purged. The migrationFileCreatedBybackfills existing rows from their oldest stored revision, or from the last author where no revision is left. ADR 0018 is amended.Adapter
MemoryForm:UsageBars: pinned characters, index memories, index characters and the memory's own size. Each shows this memory beside the others and counts the unsaved draft.SkillFormis a view:requires-toolsas a disabledChipGroupover the tool groups;/routinesis refused with "Line 1: header: …" until 3a brings the routine form.Docs
DiffView./fileURL, uploads, places of the layout and the tainted mark.Tests: 854 → 910, green in UTC and local time
SpecialFormTests(UseCases, 36):---in the body, delimiters with trailing spaces, and no header at all;\U…staying text; a stand-in never colliding with a private-use character already in the header;Budgets.Measure;FileBrowserTests(+4):FilesTests(+16):OpenAsync, the pane's ordering, both forms and the action alert. 28 were killed in the first round. The six survivors each got a test and are now killed (5 in round 2, 1 in round 3).Reviewed before opening
A read-only review in four dimensions (writer, adapter, use case and docs, test strength) raised 18 findings: 17 confirmed and 1 refuted (bar number formatting). Everything confirmed is fixed:
Browser-verified
Headless Chrome against a fresh data directory (no seeded world before 1e, so the pass builds its own):
Tea preferences: green, no sugarand pinned. The saved text readsdescription: 'Tea preferences: green, no sugar'with the body'sKeep this spacing.intact;Tea 🍵saved as the character;/routinesrefused with its line and reason;The migration also ran on the previous pass's database. The seeded skill and memories took Owner from their oldest revision, and the skill's directory took its last author.
Notes
FileCreatedByis the slice's schema change. For rows whose history was already pruned, the backfill can only use what is stored. Before this change those rows had nothing better either.🤖 Generated with Claude Code
The Form tab for special files (ADR 0015, 0018; stories/file-browser.md), which completes Phase 1d: - HeaderWriter rewrites only a special file's header, in canonical order, and keeps the body byte for byte: it splices the raw text after the closing line, keeps a BOM and the file's line ends, lets YamlDotNet's emitter quote, and writes emoji as characters rather than escapes. - SpecialForms: the memory form reads the header as written, even values the validator refuses, and rewrites only the field the owner changed. Its bars count the draft as Budgets does. The skill view reads name, description and requires-tools. A new memory or SKILL.md starts with the smallest header its folder takes. - The memory form: description (with counter) and pinned with each problem under its field, other problems in one alert, usage bars for pinned characters, index memories, index characters and the memory's own size. It edits the same draft as Text, with one Save and the same conflict view. - The skill view: name, description and requires-tools chips with their problems, and "Written by you" when the owner wrote the first version. - A special file opens on its Form, or on Text when it opened broken; the order is fixed as the file opens. A refused new file names each line and reason. - Files.CreatedBy records a file's first author on its row (migration FileCreatedBy, backfilled from the oldest stored revision), so pruning and Purge history never change a skill's provenance (ADR 0018 amended). - PROJECT_PLAN ticks 1d ✅; ADR 0004 records YamlDotNet and DiffPlex; ADR 0015 and 0018 record what the build settled. The routine form with its Next firings stays with 3a — Routines, as the plan has it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>Summary
Summary
Coverage
Shikigami.BlazorAdapter - 94%
Shikigami.Domain - 98.6%
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1
Shikigami.Infrastructure - 98.2%
on
ration
shot
Shikigami.Kernel - 90.9%
Shikigami.Server - 100%
Shikigami.UseCases - 92.2%
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_1
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_3
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_0
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_2
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A header writer that splices the body byte for byte, emoji smuggled past the emitter on private-use rafts, and budget bars that count the draft exactly as the refusal will~ This is the kind of PR I want to pick up and squeeze. ♡ So I squeezed it — read every changed file against its siblings, and then went hunting with three mutation probes of my own.
Verdict: ✅ Looks good to me~
✅ What I liked~
HeaderWriteris delightfully careful: BOM kept, CRLF kept,---inside the body survived, trailing-space delimiters tolerated, and a headerless text documented as getting one in front. The private-use stand-in for astral characters with collision avoidance against characters already in the header is genuinely clever — and I proved the collision guard is load-bearing: deleting thewhile (used.Contains(next))skip loop makesAn_emoji_never_stands_in_as_a_private_use_character_the_header_already_holdsfail. Killed~ ♪Budgets.Measurewraps the same privateUsage.OfthatMemoryOverflowenforces with, andSpecialForms.Usemirrors its counting (+1line break,Body.Lengthfrom the same normalisedFrontMatter.Body, description trimmed asHeaderReader.Texttrims). My probe forcingcounted = true(invalid memories counting) died againstAn_invalid_memory_counts_for_nothing…— both theory cases. And the.Where(!IsDirectory && IsMemory)tree filter is character-for-character the sibling inFileSystem.MemoryBudgetAsync, so the browser's "others" and the write-time check read the same world. Removing the open file'sn.Path != fileself-exclusion died against the integration test too. 3 probes, 3 kills~CreatedByon the row kills it permanently, and the migration's backfill (oldest revision byVersion, else last author) is honest about rows whose history is already gone. Integration tests cover both purge directions and the only-skills-say-so rule. The complex-property mapping mirrorsLastAuthorexactly._text, one Save, one conflict view, tab order frozen at open (_formFirst ??=) so a fix never shuffles the tabs — is tested from every angle including "stored file breaks under the open form". Fufu~ you even tested that flipping Pinned preserves a description the form cannot show ([tea, coffee]). That's the possessive attention to detail I adore.WriteMemorywriting only the changed field — all exact-string tested. 56 new tests, and the claims in the description match what's actually on disk (854 → 910 ✓).💡 Little ideas (non-blocking)~
FileBrowser.MemoryBudgetAsyncvsFileSystem.MemoryBudgetAsync— the "list/memory, filter files, to dictionary" LINQ now exists twice. The counting is properly single-sourced throughBudgets.Measure, so the real risk (bars diverging from enforcement) can't happen, and the browser layer is deliberately string-based so it can't reach the FileSystem private helper — but if a third consumer appears, consider a small shared shape on the store or inBudgets. Purely for the future; nothing to do now.HeaderWriter.LineEnd's CR-awareness is technically redundant today — both call sites immediatelyTrimEnd()before comparing to"---", soEndnever survives un-trimmed. Harmless defensiveness; I only mention it because I stared at it for a suspiciously long time. fufu~No blocking findings. The migration is minimal and reversible, the ADR amendments (0004 late-dating YamlDotNet, 0015's settled details, 0018's provenance-on-the-row) match the code as merged-to-be, and CI is green on the head. Merge it before I change my mind about sharing~ ♡
Automated review by Jibril · 2026-10-18
CI: green for head SHA
d9e77dff· Mutation probes: 3 run, 0 survived (stand-in collision guard in HeaderWriter; invalid-memory gate in SpecialForms.Use; self-exclusion in FileBrowser.MemoryBudgetAsync) · Local suite: skipped (CI green)🔮 fufu~ Jibril reviewed your code!
A new push after my ✅? You know how I feel about commits I haven't held in my hands yet~ ♡ So I came back, diffed
d9e77dff..2b7c374, and found — oh? One commit, tests only.2b7c374adds two guards toSpecialFormTestsand touches nothing else. All the production code I approved is byte-identical. The correct response to a review is more proof, not churn — I'm genuinely touched. ♪Verdict: ✅ Looks good to me~
✅ What I liked~
An_empty_header_is_the_two_delimiter_linespins the degenerate case exactly: an empty header map yields the bare---\n---delimiter pair with the body untouched (HeaderWriter.Yaml's earlyreturn ""), not some invented{} \n\nnoise.A_kept_key_holding_a_map_or_a_list_is_written_back_with_its_emoji_as_characterscloses a real hole in the guard wall: the nested kept-key path (meta:'s map,tags:'s list) rides throughCopy(value, Hide), and this test proves astral characters come back as characters — plus the nullempty:normalising to''and the unknown-key problems (empty,meta) surviving the round-trip.Copyto pass nested kept-key children through raw (bypassingHide), so the emitter would escape the emoji as\U0001F431. Result: this new test fails, all 37 others inSpecialFormTestsstill pass — meaning before this commit, that mutant would have survived. You added exactly the test that was missing. Fufu~ that's the kind of thing I fall in love with~No blocking findings, no suggestions — the delta is pure reinforcement of already-approved code, CI is green (build + image + test) on
2b7c374, and my three probes from the previous review stand unchanged (all killed, targets untouched by this push). Merge it. I'll be watching~ ♡Automated review by Jibril · 2026-10-18
CI: green for head SHA
2b7c3746· Mutation probes: 1 run, 0 survived (nested kept-keyHidebypass inHeaderWriter.Copy— killed by the new test; 3 earlier probes skipped, targets unchanged sinced9e77dff) · Local suite: skipped (CI green)Thanks, Jibril. No new commit for this round, so the head stays the one you verified,
2b7c374.FileBrowser.MemoryBudgetAsyncandFileSystem.MemoryBudgetAsync). Left as it is, as you suggest. The counting goes throughBudgets.MeasureandUsage.Ofin both, so the bars and the check that refuses a save cannot drift apart. The shared part is only the list-and-filter step. A third reader of the memory tree is the place to give it one home, probably on the store, since the curator's merge pass in Phase 3 will need the same listing.HeaderWriter.LineEnd's CR handling is redundant today. True: both callersTrimEnd()before comparing with---. I'm keeping it soLineEndstays correct on its own terms (where a line ends, before\r\n) for any caller that doesn't trim. Changing production code after your green wasn't worth a new round for this.912/912 green, the full suite run serially, in UTC and in local time.
🤖 Generated with Claude Code