Rename a tag in every note (bulk-edit job) (#101) #201

Closed
kayg wants to merge 2 commits from issue/101-rename-tag into dev
Owner

Closes #101.

What it does

  • notes.rename-tag {from, to}: renames a tag, and every tag nested under it (#work/q4 → #job/q4), in every note, including tags: in the properties. It runs as one #9 bulk-edit job: the plan is stored first, each note's room applies only the edits to the name (so they merge with anyone typing), it picks up where it stopped after a crash, and notes.undo takes it all back as one step. The request answers within 5 s; a bigger job keeps going in the background and notes.job reports when it's done.
  • notes.tag-preview {tag}: how many notes carry the tag. It only reads the note list.
  • Never touched: fenced code (also inside quotes and lists), inline code, link targets, reference definitions, web addresses and <…>. The note list, search and the rename all use the same scanner (tags/scan), so the sidebar count is exactly what a rename changes.
  • Web: open the field with Rename… in the tag's sidebar menu, or by double-clicking the row. While you type, the row shows "N notes" where its count was, so nothing shifts. Enter starts the rename, and a single toast offers Undo for every note. The row keeps its place, and an open tag list switches to the new name.

Bulk-job changes made along the way

  • Each batch now saves only its own results to a new job_steps table (history migration 4). Before, the whole plan was rewritten after every batch, which for 20k steps meant several MB per batch going through the same writer as typing.
  • While a job runs, notes.job answers from memory. Before, every poll parsed the full plan.
  • Rooms opened only to read a note during planning now close right away (Cmd::Rest), so planning thousands of notes no longer keeps thousands of rooms open.

InlineRename fixes (shared component)

  • It saves once. Before, Enter saved and then the blur saved again, and Escape could still save.
  • A live update to the list behind the field no longer resets what you're typing.
  • The field is as wide as its text, so the first letter no longer scrolls out of view.

Tests

  • Unit tests for edge cases: #tag inside words, escaped, bold, in headings, in code, and in link text vs link targets.
  • Property tests: random notes built from pieces with known right answers (catches any rewrite inside code, link targets or URLs; I confirmed it fails against a scanner with code and link skipping removed), and rename-and-back on arbitrary text.
  • Notes integration tests: rename + undo, typing during a rename is kept, a 600-note rename never stalls a typing client, and an in-process crash mid-job.
  • notes_crash.rs: the real binary is killed with SIGKILL 4× during a 400-note rename. Each tag is renamed exactly once, and code and link targets keep #hub.
  • e2e in Chromium and WebKit: rename with the count and live editor/sidebar, Undo across notes, a change from another device while the name is being typed, and Escape.

Perf numbers and screenshots are on #101.

🤖 Generated with Claude Code

https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T

Closes #101. **What it does** - `notes.rename-tag {from, to}`: renames a tag, and every tag nested under it (`#work/q4` → `#job/q4`), in every note, including `tags:` in the properties. It runs as one #9 bulk-edit job: the plan is stored first, each note's room applies only the edits to the name (so they merge with anyone typing), it picks up where it stopped after a crash, and `notes.undo` takes it all back as one step. The request answers within 5 s; a bigger job keeps going in the background and `notes.job` reports when it's done. - `notes.tag-preview {tag}`: how many notes carry the tag. It only reads the note list. - Never touched: fenced code (also inside quotes and lists), inline code, link targets, reference definitions, web addresses and `<…>`. The note list, search and the rename all use the same scanner (`tags/scan`), so the sidebar count is exactly what a rename changes. - Web: open the field with Rename… in the tag's sidebar menu, or by double-clicking the row. While you type, the row shows "N notes" where its count was, so nothing shifts. Enter starts the rename, and a single toast offers Undo for every note. The row keeps its place, and an open tag list switches to the new name. **Bulk-job changes made along the way** - Each batch now saves only its own results to a new `job_steps` table (history migration 4). Before, the whole plan was rewritten after every batch, which for 20k steps meant several MB per batch going through the same writer as typing. - While a job runs, `notes.job` answers from memory. Before, every poll parsed the full plan. - Rooms opened only to read a note during planning now close right away (`Cmd::Rest`), so planning thousands of notes no longer keeps thousands of rooms open. **InlineRename fixes (shared component)** - It saves once. Before, Enter saved and then the blur saved again, and Escape could still save. - A live update to the list behind the field no longer resets what you're typing. - The field is as wide as its text, so the first letter no longer scrolls out of view. **Tests** - Unit tests for edge cases: `#tag` inside words, escaped, bold, in headings, in code, and in link text vs link targets. - Property tests: random notes built from pieces with known right answers (catches any rewrite inside code, link targets or URLs; I confirmed it fails against a scanner with code and link skipping removed), and rename-and-back on arbitrary text. - Notes integration tests: rename + undo, typing during a rename is kept, a 600-note rename never stalls a typing client, and an in-process crash mid-job. - `notes_crash.rs`: the real binary is killed with SIGKILL 4× during a 400-note rename. Each tag is renamed exactly once, and code and link targets keep `#hub`. - e2e in Chromium and WebKit: rename with the count and live editor/sidebar, Undo across notes, a change from another device while the name is being typed, and Escape. Perf numbers and screenshots are on #101. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
`notes.rename-tag {from, to}` renames a tag, and every tag nested under
it, in every note: one bulk-edit job (planned first, applied through each
note's room, resumable after a crash, undone as one step).
`notes.tag-preview {tag}` says how many notes carry it, from the note
list alone.

- tags/scan, tags/front: one reader of tags for the list, search and
  renames. Code blocks (also in quotes and lists), inline code, link
  targets, reference definitions, web addresses and <...> are never
  tags, so a rename never touches them.
- tags/rewrite: minimal edits (the name only), checked by property tests
  built from pieces with known answers, and by rename-and-back on any
  text.
- Bulk jobs save each batch's results in a small job_steps table instead
  of rewriting the whole plan, and answer notes.job from memory while
  they run (a 20k-step plan is several MB).
- A job that only reads a note lets its room close at once (Cmd::Rest),
  so planning thousands of notes keeps few rooms open.
- The request answers within 5 s; a bigger job goes on in the
  background and notes.job tells when it is done.
- Tests: rename + undo, typing during a rename, a big rename never
  stalls typing, a crash mid-job, and SIGKILL of the real binary again
  and again mid-rename (each tag renamed exactly once). Benchmark:
  examples/tag_bench on the perf fixture Homes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Web: rename a tag from the sidebar, with a count and Undo (#101)
Some checks failed
CI / rust (pull_request) Successful in 2m8s
CI / web (pull_request) Failing after 8m39s
ca7633bae7
Rename… in a tag's menu (or a double-click on its row) opens the name in
place; while you type, the row says how many notes will change, where
its count was, so nothing moves. Enter renames it in every note on the
server; the row keeps its spot, an open list follows the new name, and
one toast offers Undo for every note at once. A long rename says it's
working first.

InlineRename fixes found on the way: it saves once (Enter, then the
field's blur on the way out, saved twice; Escape could still save), a
live change to the list behind it no longer resets what's being typed,
and the field is as wide as its text instead of scrolling the first
letter away. Double-click on a sidebar tag reaches the row again (the
row holds the pointer to drag, so the name never saw it).

e2e (Chromium and WebKit): rename with count and live editor/sidebar,
Undo across notes, a change from another device while typing the name,
and Escape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Author
Owner

Superseded by the -2 branch PR (adds the in-place sidebar rename); closing.

Superseded by the -2 branch PR (adds the in-place sidebar rename); closing.
kayg closed this pull request 2026-10-14 09:04:19 +00:00
Some checks failed
CI / rust (pull_request) Successful in 2m8s
CI / web (pull_request) Failing after 8m39s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!201
No description provided.