Skip to content

Add durable bookmarks across CLI and TUI#121

Open
tony wants to merge 25 commits into
issue-80-deterministic-identitiesfrom
issue-79-bookmarks
Open

Add durable bookmarks across CLI and TUI#121
tony wants to merge 25 commits into
issue-80-deterministic-identitiesfrom
issue-79-bookmarks

Conversation

@tony

@tony tony commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Add durable, private bookmarks keyed by canonical content, stored-occurrence, or thread handles without copying prompt bodies or modifying source histories.

  • Add agentgrep bookmark add|remove|list with idempotent human and JSON workflows, a versioned 200-entry snapshot, private XDG storage, atomic replacement, and symlink-safe locking.
  • Add a focus-safe HUD b binding, /bookmark [record|content|thread], and a compact /bookmarks recall list with resolved and unresolved entries.
  • Reopen recalled records in detail without replacing the current results or sticky filter.
  • Keep bookmark load, mutation, resolution, identity, and large detail/find work off the Textual message pump. Rich rendering is bounded to 64 KiB while full-source match offsets and JSON syntax color remain exact.
  • Keep storage and UI diagnostics free of prompt bodies and local paths.

Relationship

This PR targets the branch for #111 because deterministic record identities are its prerequisite. It is independent of the export PR and can be retargeted to master after #111 lands.

Closes #79

Tested

  • Full repository gate: Ruff, formatting, ty, 2,769 passed, 3 skipped, 3 expected failures, and Sphinx.
  • Adversarial bookmark/detail/non-blocking review, including races, stale generations, capacity, symlink refusal, multi-megabyte plain and JSON bodies, active find, resize, teardown, and all three scopes.
  • Live Textual pane: focus-safe input, add/recall/filter/reopen, starred identity, narrow and zoomed layout, persistence across restart, unresolved-scan cancellation, private modes, unchanged source stores, and clean exit.

@tony tony force-pushed the issue-80-deterministic-identities branch from adcf5e0 to cb6377d Compare July 12, 2026 20:14
@tony tony force-pushed the issue-79-bookmarks branch from bb2c2c8 to 9a9190e Compare July 12, 2026 20:15
@tony tony force-pushed the issue-80-deterministic-identities branch 9 times, most recently from a68c683 to 5ffd718 Compare July 14, 2026 03:56
@tony tony force-pushed the issue-79-bookmarks branch from 9a9190e to e1497d2 Compare July 14, 2026 04:27
@tony tony force-pushed the issue-80-deterministic-identities branch from 5ffd718 to 4e49bbd Compare July 14, 2026 23:36
tony added 12 commits July 14, 2026 18:37
why: Canonical records need a bounded, privacy-minimal bookmark layer that survives CLI and later TUI sessions without mutating agent-owned stores.

what:
- Add strict versioned bookmark values and transactional XDG persistence.
- Add idempotent add/remove/list CLI actions with deterministic output.
- Cover validation, durability, permissions, atomicity, and safe errors.
why: Existing snapshots could retain permissive modes, direct callers
could receive path-bearing filesystem errors, and custom stores changed
caller-owned parent permissions.

what:
- Repair snapshot modes through the descriptor held under the stable lock.
- Translate storage failures into generic path-free, unchained errors.
- Preserve existing explicit parent modes while securing owned directories.
- Cover each review finding with focused regression tests.
why: Canonical bookmarks need a keyboard-first HUD surface.
Saved targets must resolve without freezing Textual or replacing active
search state.

what:
- Add bookmark slash commands, focus-safe toggling, and a compact recall
  modal.
- Offload bookmark loading, identity work, transactions, and target
  resolution.
- Gate writes and callbacks while preserving results and marking saved
  records.
- Cover modal behavior, races, privacy, pump bounds, and guards.
why: Recalled records can carry multi-megabyte bodies, and preparing their line-capped detail snapshot on the Textual pump freezes every interaction.

what:
- Defer large uncached body truncation to the existing detail worker.
- Keep pending find state bounded while preserving generation and cache gates.
- Cover single-line and multiline recall under the runtime watchdog.
why: Recall could snapshot entries before an accepted write became durable, while a concurrent toggle left an older resolver able to open a stale modal.

what:
- Reject recall while a bookmark transaction is pending.
- Cancel and invalidate active resolution before launching a write.
- Cover both mutation and resolution race interleavings.
why: First-hit resolver coverage and tiny modal fixtures could miss regressions at the fixed 200-entry boundary.

what:
- Mount and navigate a full-capacity filtered recall modal.
- Exercise target-last and target-absent resolver scans under the watchdog.
- Assert every candidate hashes off-pump without a heartbeat stall.
why: A find query opened while large-body preparation was pending searched a bounded prefix but retained those incomplete matches after the final body arrived.

what:
- Recompute live find state when worker-prepared detail becomes visible.
- Cover a match beyond the pending prefix and retain existing pending behavior.
why: Final large-detail preparation re-entered the complete find
pipeline on the Textual pump. Multi-megabyte sources could freeze
input during matching, copying, highlighting, and scroll mapping.

what:
- Add an independent generation-gated detail-find worker for large
  sources.
- Prepare capped matches, cached bases, overlays, and scroll targets
  off-pump.
- Cover active final apply, stale generations, rapid steps,
  invalidation, and teardown.
why: Prefix-only find applies have no matches to wrap against.
Replacing the requested target with that temporary current index loses
steps before the final large source arrives.

what:
- Keep the unwrapped requested target across prepared find applies.
- Cover two pending steps, final match selection, record restore, and
  explicit query reset.
why: A draining find worker carries scroll geometry for its captured
detail width. Applying that result after a resize can move the current
match outside the resized viewport.

what:
- Carry detail width through the prepared result and reject mismatches.
- Restart active find after the debounced resize and self-correct layout
  drift without scanning on the pump.
- Cover old-width rejection and the corrected scroll request.
why: A large-detail find could finish during the resize debounce and start a
replacement before the post-layout callback started a second replacement.
Stale callbacks could also survive an intervening query or record lifecycle.

what:
- Invalidate detail-find work immediately when resize begins.
- Carry a generation token through the post-refresh restart callback.
- Cover exact-one replacement and stale lifecycle callback races.
why: Durable bookmarks span CLI and HUD behavior and need one
privacy-conscious contract for canonical targets, local state, and
current-store recall.

what:
- Add the canonical scope, storage, and CLI guide with ADR 0016.
- Document HUD toggles, filtering, resolution, and unresolved retention.
- Add the release deliverable, indexes, and documentation contract
  tests.
tony added 13 commits July 14, 2026 18:37
why: Record additions require a content validator while removals do not.
A re-add also depends on matching the saved validator, even at capacity.

what:
- Scope --content-id to record add and document remove syntax.
- Explain matching re-adds and mismatched-validator exit behavior.
- Pin the distinction in the bookmark documentation contract.
why: Long canonical IDs made the record-add example exceed the command
width used by the documentation.

what:
- Put the content-ID flag and record target on continuation lines.
- Keep the console block as one copyable shell command.
why: Bookmark titles and bodies may contain Textual markup-like text. Parsing
that content could alter the preview or abort modal mounting.

what:
- Disable markup parsing for the bookmark preview widget.
- Mount a malformed-markup title and assert it renders literally.
why: Following a sidecar-lock symlink could change and lock an unrelated
file. Non-regular descriptors are also invalid lock transaction anchors.

what:
- Open the lock without following symlinks and verify its descriptor type.
- Refuse unsafe locks before chmod or flock with path-free public errors.
- Preserve a symlink target byte-for-byte and cover non-regular descriptors.
why: The bookmark TUI depended on the package facade in violation of the
module boundary, while the public Task 1 dispatcher was not re-exported
like its sibling command dispatchers.

what:
- Import bookmarks from its owning module in the HUD.
- Re-export the bookmark dispatcher through the package facade.
- Pin only BookmarkArgs and run_bookmark_command in the facade snapshot.
why: Inserting the bookmark parser among required ParserBundle fields broke
callers that construct the longstanding four-parser bundle positionally or
by keyword.

what:
- Append the bookmark parser as an optional compatibility field.
- Keep internally-created bundles wired to the bookmark-specific parser.
- Cover the original positional and keyword constructor forms.
why: A mutation may finish after the live detail record has been rebuilt as
a distinct Python object with the same canonical identity. Object comparison
left that equivalent record unstarred.

what:
- Match mutation refreshes against the cached canonical record ID.
- Keep unrelated live selections untouched without hashing on the pump.
- Mount equivalent and unrelated records around a mutation regression.
why: Cached detail selections invalidated an inactive find group twice,
and a subsequent search reset repeated the no-op cancellation.

what:
- Cancel find preparation only while find state is active.
- Let the live find refresh own final-body invalidation.
why: Offloading a multi-megabyte Rich body still lets GIL-bound
highlighting and the final Static update stall the Textual pump.

what:
- Keep complete find sources and absolute match coordinates.
- Render bounded initial and match-centered detail excerpts.
- Preserve formatted JSON coordinates and legacy body helpers.
- Cover watchdog, edge-match, and large-copy bounds.
why: Match-centered JSON excerpts include omission markers, so sniffing the
slice as standalone JSON drops syntax token colors.

what:
- Carry the complete-source format through detail and find snapshots.
- Detect legacy formats before slicing inside find preparation.
- Assert large JSON excerpts retain token and match styles.
why: Wall-clock heartbeat gaps conflate a blocked Textual pump with host
CPU descheduling, making off-pump bookmark tests flaky on shared runners.

what:
- Hold resolver and detail workers on GIL-releasing events.
- Prove pump progress through a scheduled callback and live key handling.
- Keep watchdog runtime and dedicated watchdog tests unchanged.
why: Positive pump probes replaced scheduler-sensitive warning checks, but
the enabled app-shell watchdog wiring still needs deterministic coverage.

what:
- Spy watchdog startup while mounting the real explorer shell.
- Observe a scheduled heartbeat callback on the pump thread.
- Prove teardown stops the watchdog and interval timer.
why: The bookmark changelog test pinned the published a34 section, so it missed the issue 79 deliverable after the unreleased section advanced.

what:
- Discover the current unreleased version from its heading.
- Verify the unreleased section and bookmark deliverable.
@tony tony force-pushed the issue-79-bookmarks branch from e1497d2 to beef6e6 Compare July 14, 2026 23:37
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.

1 participant