207 lines
10 KiB
Markdown
207 lines
10 KiB
Markdown
# Agent handoff — cross-machine continuity
|
||
|
||
**Last updated: 2026-07-11, on the laptop, by the auto-pairing
|
||
session.** This file is the bridge between development machines. If you
|
||
are an agent reading this on a fresh clone: this document plus the
|
||
`docs/*-framing.md` files ARE your memory. Read this fully before
|
||
taking on work, seed your persistent memory from it, and **update this
|
||
file (and commit it) whenever project state changes materially** — the
|
||
next machine reads it the way you just did.
|
||
|
||
## 1. Where the project stands (2026-07-11)
|
||
|
||
- `main` @ `7e127ab` (auto-indent #109 merged), protocol **v15**
|
||
(`SUPPORTED=[6..15]`).
|
||
- **Auto-pairing (Arc 2, last item) in flight on this branch** —
|
||
framing `docs/auto-pairing-framing.md` at revision 3 (two
|
||
pre-branch review rounds). Shape: pair chars leave both frontends'
|
||
optimistic classifiers (dispatch-routed, daemon-peer undo units);
|
||
reaction hook in `builtin/runtime/pair.lua` loaded BEFORE lsp.lua;
|
||
exact one-shot typed-edit provenance via
|
||
`pmacs.editor.take_typed_edit()` (Q#AP9). When this merges, Arc 2
|
||
closes.
|
||
- Auto-indent (#109) landed: RET binds `edit.newline-and-indent`;
|
||
plain Enter round-trips on both frontends. Rode along: shared
|
||
search invalidation across dispatch/notification/undo/redo (Q#AI8),
|
||
empty-selection clearing after successful inserts (Q#AI9). The
|
||
TUI's missing nonempty-selection optimistic type-over gate and
|
||
generated-buffer search invalidation remain named deferrals (§6).
|
||
- Roadmap: `docs/roadmap-2026-07.md` (ranked arcs). Position:
|
||
- **Arc 1 (LSP utility surface) COMPLETE** — completion popup
|
||
(#92/#93), panels/references/outline/hover (#94–#96), plus
|
||
hardening follow-ups (#102, #105, #106).
|
||
- **Arc 2 (editing table stakes)** — query-replace (#97), kill ring
|
||
+ `M-y` (#103/#105/#106), comment-toggle (#107), auto-indent
|
||
(#109), auto-pairing (this branch — the last item).
|
||
- **Arc 3 (persistence) COMPLETE** — saveplace/recentf (#98),
|
||
desktop-save (#99), autosave/crash-recovery (#100), save-clobber
|
||
fix (#101).
|
||
- **After Arc 2 closes**: the user wants a decision discussion —
|
||
compile-mode (Arc 5 stage 1) vs themes (Arc 4). Do not pick
|
||
unilaterally; frame the tradeoff and ask.
|
||
|
||
## 2. How we work (the part that must not drift)
|
||
|
||
The user is expert and reviews deeply — they falsify framings and find
|
||
real bugs in round after round. The cadence that has worked for ~40 PRs:
|
||
|
||
1. **Scout ground truth** in the code before proposing anything.
|
||
2. **Write a framing doc** — `docs/<feature>-framing.md`, numbered
|
||
decisions (`Q#XY1…`), explicit "Ground truth", "Bets", "Deferred
|
||
(named)", and an acceptance-test list. Present it and **wait for
|
||
explicit approval** ("Go for it" / "Ready to roll"). Expect 1–3
|
||
rounds of findings first; revise the doc, don't argue.
|
||
3. **Branch off main** (one feature = one branch = one PR). Commit the
|
||
framing as the first commit.
|
||
4. Implement. **Every reviewer finding gets a bite-verified fix** — a
|
||
test that fails without the fix. Watch for vacuously-passing tests.
|
||
5. Run the full gate suite (§3). Open the PR with `gh`.
|
||
6. The user replies with "Findings" lists on the PR rounds too. Same
|
||
discipline. They say when to merge — **never merge unprompted**.
|
||
7. After merge: update this handoff + your memory.
|
||
|
||
Commit/PR conventions: commit messages via `git commit -F <file>` (no
|
||
inline backticks through the shell); end with the Claude co-author
|
||
line. PR bodies end with the Claude Code attribution. Clippy runs as
|
||
its own step, never `&&`-chained.
|
||
|
||
## 3. Gate suite (all green before any PR)
|
||
|
||
```
|
||
cargo fmt --check
|
||
cargo clippy --workspace --all-targets -- -D warnings # own step
|
||
cargo test --lib # ~1500
|
||
cargo test --lib --features crdt # ~1672
|
||
cargo test --test <the new/touched acceptance suites>
|
||
cargo test --test m4_acceptance -- --skip basedpyright
|
||
PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu # 58
|
||
cargo test --workspace -- --skip basedpyright # full sweep
|
||
git diff --check
|
||
```
|
||
|
||
Machine-specific caveats — re-verify on a machine you haven't used
|
||
before trusting them:
|
||
|
||
- **basedpyright**: the DESKTOP's local binary is broken and HANGS the
|
||
`m4_5_basedpyright` tests — hence the `--skip` there. The LAPTOP has
|
||
a working basedpyright 1.39.9 (verified 2026-07-10: the m4_5 test
|
||
passes in 0.18s), so the skip is droppable on the laptop.
|
||
- **GPU on the laptop**: AMD Radeon 780M (RADV) — native Vulkan,
|
||
`PMACS_REQUIRE_GPU=1` works without lavapipe.
|
||
- **m8 daemon tests are FLAKY** (timing). A lone m8 failure → rerun
|
||
before investigating.
|
||
- **GPU tests** need a Vulkan device. `PMACS_REQUIRE_GPU=1` makes
|
||
absence a hard failure instead of a silent skip. Headless option:
|
||
lavapipe (see `docs/repository-audit-2026-07-03.md` for the CI
|
||
harness how-to). On a laptop without discrete GPU, mesa/lavapipe
|
||
works.
|
||
- **The desktop's shell is fish** (no `$(...)`, use `(...)`; no
|
||
`$UID`, use `(id -u)`). Check `$SHELL` here before assuming.
|
||
|
||
## 4. Substrate invariants (do not undo; tests enforce most of these)
|
||
|
||
**Command boundaries (Arc 2 kill-ring substrate)** —
|
||
`EditorCore.command_history: HashMap<FrontendId, CommandBoundary{this, last}>`,
|
||
per frontend. Rotate on: keybound command, self-insert, menu invoke,
|
||
`invoke_interactive` (the M-x path). Break on: unbound key, GPU
|
||
optimistic CRDT edits, pointer gestures (wheel scroll deliberately does
|
||
NOT break), unified paste. **Plain `pmacs.command.invoke` stamps
|
||
nothing** (programmatic API); `invoke_interactive` rotates-then-invokes
|
||
(Emacs M-x semantics). Single-codepoint optimistic CRDT inserts
|
||
classify as `buffer.self-insert` (exact decode; `"a("` breaks instead).
|
||
Lua: `ed.this_command()` / `ed.last_command()`.
|
||
|
||
**Effective-edit returns** — `buf:insert/delete/replace` return the
|
||
post-intercept `(start, end, inserted_len)`. Callers that care
|
||
(killring, comment) compare EXACTLY against the request; length-delta
|
||
and text-at-position checks are documented defeated patterns. Always
|
||
`pcall` the mutator: a rejecting intercept must report, not throw
|
||
through, and failed ops must leave no state (kill chains, yank
|
||
sessions).
|
||
|
||
**Kill ring** — entries `{id, text}` with stable monotonic ids; chains
|
||
and yank sessions are per-frontend and id-checked (an index is not
|
||
stable under other frontends' pushes). OS clipboard mirrors to the
|
||
acting frontend only. Paste is a unified daemon arm keyed by the
|
||
dispatcher's AUTHENTICATED source — never a payload frontend_id.
|
||
|
||
**LSP outbound positions** — every Position/Range builder in
|
||
`src/lsp.rs` routes through `outbound_position` (byte → negotiated
|
||
encoding). Any new request builder must too; UTF-16 servers reject raw
|
||
byte columns on non-ASCII text. Semantic tokens: `full`, `full.delta`,
|
||
and `range` are three INDEPENDENT capabilities — gate each.
|
||
|
||
**Persistence (Arc 3)** — state-dir wiring lives in
|
||
`install_state_dirs()` on real entry points only, NOT `EditorState::new()`
|
||
(tests stay hermetic); `PMACS_STATE_HOME` overrides. Autosave: one
|
||
buffer owns a path's recovery slot; only recover/discard release
|
||
unclaimed crash data; adopt clears the old owner's skip cache.
|
||
|
||
**Protocol** — encoding-breaking bumps are deliberate and versioned
|
||
(`SUPPORTED=[6..15]`). v15 = `CompletionPopup` + `StatusFacts.message`.
|
||
New wire surface ⇒ bump + both-frontends support + acceptance.
|
||
|
||
**Fake LSP** (`src/bin/pmacs_fake_lsp.rs`) modes: `fullonly`,
|
||
`rangeonly`, `rangeonly16` (UTF-16 + fail-closed bounds validation),
|
||
`sighelp`. Use these for capability-matrix tests, not real servers.
|
||
|
||
## 5. Hard-won ops lessons
|
||
|
||
- **The checkout may be shared with the user.** Check `git status` for
|
||
foreign uncommitted work before any stash/checkout/branch surgery;
|
||
never assume dirty files are yours. (Their uncommitted fix was nearly
|
||
orphaned once.)
|
||
- **Stacked PRs**: retarget the child to main BEFORE merging the
|
||
parent — GitHub auto-closes a PR whose base branch is deleted and
|
||
cannot reopen it (#104 → re-opened as #105).
|
||
- **Scripted edits (sed/python) in files with repeated similar blocks**
|
||
(`src/lsp.rs` JSON builders): anchor on a unique line or you will
|
||
silently edit the wrong block. This produced a vacuously-passing test
|
||
and cost an hour.
|
||
- **Acceptance fixtures that open `.rs`/`.py` files** must empty
|
||
`pmacs.lsp.config` first — the after-load hook spawns real servers
|
||
(rust/python/c have default configs). Language *detection*
|
||
(grammars + `pmacs.lsp.filetypes`) is unaffected.
|
||
- Test scratch buffers have no path ⇒ no language; use tempdir files
|
||
when language matters.
|
||
|
||
## 6. Named deferrals (the standing backlog, consolidated)
|
||
|
||
Editing: word kills (`M-d`/`M-BS` — need bytes-returning deleters +
|
||
prepend-on-backward append), `C-SPC` set-mark, `C-u C-y` / `C-M-w`,
|
||
kill-ring browser + persistence, clipboard watching, block comments +
|
||
mid-line comment spans, comment-dwim append-at-EOL, per-language
|
||
comment padding.
|
||
Substrate: buffer-aware edit epoch (after-edit currently compares the
|
||
ACTIVE buffer only), wire provenance for CRDT self-insert
|
||
classification, Lua intercept probe, completion.lua still on the old
|
||
cursor-delta heuristic (migrate to `this_command`), the TUI's
|
||
nonempty-selection optimistic type-over gate, generated-buffer search
|
||
invalidation, cross-peer chronological undo arbitration (mixed
|
||
source/daemon history; pinned by auto-pairing acceptance),
|
||
origin-pinned `buffer.after-edit` fan-out (a context-switching
|
||
intercept changes what later callbacks — LSP, completion — observe).
|
||
LSP/persistence: hidden-buffer LSP attach, daemon desktop-restore, the
|
||
*warning* half of external-change detection (verify-visited-file-
|
||
modtime), config registry (no unified config surface yet).
|
||
GPU: auto-reconnect after daemon restart, splits/multi-buffer, gutter
|
||
riders (whitespace guides, folding, git markers).
|
||
Housekeeping: F-016 `lua_bindings/mod.rs` split paused mid-way
|
||
(tranches 0–2 landed, ~5–8 PRs left; see
|
||
`docs/lua-bindings-split-framing.md`).
|
||
|
||
## 7. Machine-local facts (desktop) that do NOT travel
|
||
|
||
Three untracked files live only on the desktop working tree and are
|
||
deliberately never committed: `docs/pmacs-gpu-editing-perf-handoff.md`,
|
||
`docs/session-5-stale-styling-handover.md`, `python_experiment.md`.
|
||
Don't expect them in a clone; on the desktop, never delete them.
|
||
|
||
## 8. Update protocol for this file
|
||
|
||
When a PR merges, an arc opens/closes, or a decision lands: edit the
|
||
snapshot (§1), append lessons (§5) and deferrals (§6) as they arise,
|
||
bump the date line at the top, and commit — usually riding the same PR
|
||
as the work. Keep it under ~250 lines: this is a briefing, not a log;
|
||
prune sections that stop being true.
|