Skip to content

feat: in-app frontmatter editing — the drawer becomes a guarded write site - #82

Merged
samkeen merged 2 commits into
mainfrom
claude/gh-79-review-5d8e7q
Jul 24, 2026
Merged

feat: in-app frontmatter editing — the drawer becomes a guarded write site#82
samkeen merged 2 commits into
mainfrom
claude/gh-79-review-5d8e7q

Conversation

@samkeen

@samkeen samkeen commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Closes #79.

The frontmatter drawer grows an Edit affordance: the raw YAML in a plain textarea with explicit Save (⌘⏎/⌘S) / Cancel (Esc), backed by one new façade op — Vault::write_frontmatter(note_ref, fm_yaml, base_revision), Vault::write's frontmatter sibling. Raw-text-first by design: a form round-trip would be a silent rewrite of the human's YAML (W4/W5), so B2 splices the buffer verbatim between the fences and judges nothing.

Core (b2-core)

  • ParsedNote::replace_frontmatter — the byte-honest splice mirroring replace_body: only the bytes between the fences change; every body byte is preserved by construction. The one mechanical touch is a missing final newline (so the closing fence stays on its own line).

  • Vault::write_frontmatter — same shape as write: whole-file content-hash revision guard (body saves and frontmatter saves guard each other with no new concurrency story), splice, model-free re-projection. An unchanged body keeps its chunks and vectors — a frontmatter save never re-embeds; the notes row and typed edges re-derive from the new block.

  • Two refusals, validated on the re-parsed spliced candidate before any byte reaches disk:

    • a changed/removed/duplicated b2id (new Error::FrontmatterIdentity) — L1, the one line B2 protects. Re-quoting the same id passes (identity, not bytes);
    • a top-level --- line (Error::Frontmatter) — it would close the block early and leak the rest into the body, and the body is not this op's to change.

    Everything else saves — malformed YAML included: it round-trips verbatim and projects best-effort, exactly as the same edit made in vim would.

  • Warn-don't-block surfacing: ParsedNote::frontmatter_readable + NoteView.frontmatter_readable flag a block that doesn't read as YAML metadata. Computed at read time, so the fs-watch reconcile path raises the identical warning for an external hand-edit — the case In-app frontmatter editing — a self-healing FM write site #79 is really hardening against (the Invalid YAML frontmatter triggers a b2id re-stamp loop (file grows every reindex; desktop can loop on it) #75 self-healing stance, extended).

Adapters (thin, per E3)

  • desktop: one write_frontmatter command (model-free, write_note's posture, outside the embed slot) + actionable error mappings; CLI: matching user_message arms.
  • ui: drawer Edit → mini-editor with inline refusal messages, a Reload / Keep-mine conflict strip, a ⚠︎ flag on the drawer toggle for unreadable YAML, and the render carve-out + edit guards so the live buffer is never rebuilt or silently discarded (keyboard paths throughout, per K1). No autosave, deliberately: half-typed YAML isn't a body sentence, and the b2id guard wants a deliberate save to refuse cleanly.

Docs

W3's on-command write enumeration (invariants.md), the root CLAUDE.md write list, and the desktop CLAUDE.md model-free-ops list gain the new write site — deliberate amendments, sanctioned by #79.

Tests

8 new core integration tests (tests/write_frontmatter.rs: byte-honesty, identity guard, fence refusal, conflict chain, malformed-YAML round-trip + heal, edges-without-reembed, model-free posture, mixed body/FM revision chaining), 5 new note.rs unit tests, 2 new desktop command tests. Full core suite (31 binaries), desktop suite (44 tests), clippy, fmt, tsc + vite build, and the frontend pure-logic suite all green.

Extracted follow-ups (so this closes #79 cleanly)

🤖 Generated with Claude Code

https://claude.ai/code/session_013nEUZMKhLH5DRUT2z3g2Nr


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added a frontmatter mini-editor (Save, Cancel, Reload, Keep Mine) with an unreadable warning and inline conflict/error messaging.
    • Introduced model-free frontmatter saving that preserves the note body and maintains revision conflict contracts.
    • Extended UI/IPC to support frontmatter-only writes and to reflect frontmatter readability.
  • Bug Fixes

    • Improved frontmatter parsing/readability detection while keeping raw bytes round-trippable.
    • Enforced protection of the note identity by refusing frontmatter edits that change, remove, or duplicate b2id.
    • Strengthened write validation (including fence-line --- handling) and revision chaining behavior.
  • Tests

    • Added integration coverage for frontmatter write invariants and edge cases.

… site (#79)

The frontmatter drawer grows an Edit affordance: the raw YAML in a plain
textarea with explicit Save/Cancel, backed by one new façade op —
`Vault::write_frontmatter(note_ref, fm_yaml, base_revision)`, `Vault::write`'s
frontmatter sibling. Raw-text-first by design: a form round-trip would be a
silent rewrite of the human's YAML (W4/W5), so B2 splices the buffer verbatim
between the fences and judges nothing.

Core (`b2-core`):
- `ParsedNote::replace_frontmatter` — the byte-honest splice mirroring
  `replace_body`: only the bytes between the fences change; every body byte is
  preserved by construction. The one mechanical touch is a missing final
  newline (so the closing fence stays on its own line).
- `Vault::write_frontmatter` — same shape as `write`: whole-file content-hash
  revision guard (so body saves and frontmatter saves guard each other with no
  new concurrency story), splice, model-free re-projection. An unchanged body
  keeps its chunks and vectors — a frontmatter save never re-embeds.
- Two refusals, validated on the re-parsed spliced candidate before any byte
  reaches disk: a changed/removed/duplicated `b2id` (new
  `Error::FrontmatterIdentity` — L1, the one line B2 protects) and a top-level
  `---` line (it would close the block early and leak into the body).
  Everything else saves — including malformed YAML, which is the human's to
  fix, exactly as the same edit in an external editor would be.
- `ParsedNote::frontmatter_readable` + `NoteView.frontmatter_readable` — the
  warn-don't-block half: a block that fails to read as YAML metadata surfaces
  as a non-blocking notice. Computed at read time, so the fs-watch reconcile
  path raises the identical warning for an external hand-edit — the case #79
  is really hardening against (the #75 self-healing stance extended).

Adapters (thin, per E3):
- desktop: `write_frontmatter` command (model-free, `write_note`'s posture),
  actionable error mappings; CLI: matching `user_message` arms.
- ui: the drawer's Edit → mini-editor with Save (⌘⏎/⌘S) / Cancel (Esc),
  inline refusal + conflict (Reload / Keep mine) strips, the unreadable-YAML
  flag on the toggle, and the render carve-out + edit guards so the live
  buffer is never rebuilt or silently discarded (K1-conforming keyboard paths).

Docs: W3's on-command write enumeration, the root CLAUDE.md write list, and
the desktop CLAUDE.md model-free-ops list gain the new write site.

Tests: 8 new core integration tests (byte-honesty, identity guard, conflict
chain, malformed-YAML round-trip, edges-without-reembed, model-free), 5 new
note.rs unit tests, 2 new desktop command tests. Whole core + desktop suites
green; ui `tsc`/vite build and pure-logic suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nEUZMKhLH5DRUT2z3g2Nr
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17a40c71-1821-4b76-ba6d-e2481444745c

📥 Commits

Reviewing files that changed from the base of the PR and between f5f48a5 and 65ccfb9.

📒 Files selected for processing (1)
  • ui/src/main.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • ui/src/main.ts

📝 Walkthrough

Walkthrough

Adds guarded frontmatter persistence with YAML readability tracking, identity and revision validation, desktop IPC wiring, and a frontend mini-editor with save, conflict handling, navigation guards, and unreadable-frontmatter warnings.

Changes

Frontmatter editing

Layer / File(s) Summary
Core frontmatter parsing and contracts
crates/b2-core/src/error.rs, crates/b2-core/src/note.rs, crates/b2-core/src/vault.rs
Tracks YAML readability, centralizes reparsing, preserves raw bytes, validates top-level b2id, and exposes readability through NoteView.
Guarded frontmatter persistence
crates/b2-core/src/vault.rs, crates/b2-core/tests/write_frontmatter.rs, CLAUDE.md, docs/design/invariants.md
Adds revision-guarded frontmatter splicing that preserves the body, rejects fence and identity violations, reprojects metadata, and tests malformed YAML, relations, vectors, projection-only vaults, and chained writes.
Desktop command and error wiring
crates/b2-cli/src/main.rs, crates/b2-desktop/src/commands.rs, crates/b2-desktop/src/error.rs, crates/b2-desktop/src/main.rs, crates/b2-desktop/CLAUDE.md
Exposes write_frontmatter through Tauri, delegates through a model-free vault, maps errors, registers the command, and updates wiring documentation.
Frontend mini-editor UI
ui/src/api.ts, ui/src/main.ts, ui/src/render.ts, ui/src/state.ts, ui/src/types.ts, ui/style.css
Adds raw YAML editing, save/cancel/reload/keep-mine controls, conflict handling, edit guards, readability warnings, IPC integration, and styling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

Poem

A rabbit edits YAML with care,
Keeps every byte exactly there.
The b2id stays, the body rests,
Conflicts hop through guarded nests.
Save and reproject bright—
Frontmatter lands just right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding in-app frontmatter editing as a guarded write path.
Linked Issues check ✅ Passed The PR implements byte-preserving frontmatter editing with revision guards, b2id protection, warnings, and UI/command support for #79.
Out of Scope Changes check ✅ Passed The changes are focused on frontmatter editing, related guards, UI, docs, and tests; no unrelated scope appears introduced.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/gh-79-review-5d8e7q

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/src/main.ts`:
- Around line 1470-1472: Update the analogous post-embed reread in
autoIndexOnOpen to require both !state.editing and !state.fmEditing before
calling api.readNote, preserving the existing reread behavior when neither
editor is active.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e6666bb-322e-4bc9-9575-cefbe841ecbb

📥 Commits

Reviewing files that changed from the base of the PR and between 7978842 and f5f48a5.

📒 Files selected for processing (17)
  • CLAUDE.md
  • crates/b2-cli/src/main.rs
  • crates/b2-core/src/error.rs
  • crates/b2-core/src/note.rs
  • crates/b2-core/src/vault.rs
  • crates/b2-core/tests/write_frontmatter.rs
  • crates/b2-desktop/CLAUDE.md
  • crates/b2-desktop/src/commands.rs
  • crates/b2-desktop/src/error.rs
  • crates/b2-desktop/src/main.rs
  • docs/design/invariants.md
  • ui/src/api.ts
  • ui/src/main.ts
  • ui/src/render.ts
  • ui/src/state.ts
  • ui/src/types.ts
  • ui/style.css

Comment thread ui/src/main.ts
…ontmatter edit

The auto-index pass runs unbidden on vault open, and its post-embed re-read of
the open note adopted a fresh revision under whatever editor was live. The body
editor was already excluded; the frontmatter mini-editor (#79) was not — so a
frontmatter save begun before auto-indexing finished could chain the adopted
revision and silently overwrite a concurrent disk change instead of conflicting.
Same carve-out as reconcileExternalChange and doReindex. (CodeRabbit, PR #82.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nEUZMKhLH5DRUT2z3g2Nr
@samkeen
samkeen merged commit 39f662f into main Jul 24, 2026
1 check passed
@samkeen
samkeen deleted the claude/gh-79-review-5d8e7q branch July 24, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

In-app frontmatter editing — a self-healing FM write site

2 participants