docs: frame git integration Stage 1 (revision 5, approved)
COHERENCE.md section 15 grades contextual affordances "weak" and says
the Git affordance list "has nothing to attach to yet". For a daily
driver this is the largest remaining gap --- not the deepest (sections 7
and 9 are), but the one a user touches every working hour.
Stage 1 is read-only and panel-based: a `*git-status*` listview over
`--porcelain=v2 --branch -z`, and a file-level `*git-diff*` in plain
generated text. THE STAGE LINE FALLS AT THE WIRE, and that is a
scheduling decision as much as a design one: `DecorationKind` is a
closed enum, so gutter markers need new variants and a
PROTOCOL_VERSION bump. Bumps are a strict serialization point --- this
session recorded eight broken assertions from one --- so Stage 1
touching no wire is what lets it run beside other lanes, and Stage 2
must be scheduled alone.
FOUR REVIEW ROUNDS, and the doc records what each one caught, because
the pattern is the useful part:
- `ProjectKind::Git` means a BARE git repo; a language marker beside
`.git` wins, so this very repository reports "rust". A `kind ==
"git"` gate would have failed on the repo it was written in. The
rule is now: never ask pmacs whether it is a git repo --- run git
and let it resolve its own worktree.
- `tests/fixtures/pmacs-magit/` already exists: 1,914 lines, a
porcelain-v2 parser, 32 tests. Section 15's "no Git integration
anywhere in the tree" is literally false; the PRODUCT gap is real.
The tokenizer is deliberately REWRITTEN for `-z` rather than
ported --- newline-delimited and NUL-delimited v2 are different
grammars.
- `listview.open` resets collapse and always seats line 1, so
selection preservation is the consumer's job, not the primitive's.
And `d` is not on its key surface; binding it needs an additive
`keys` table, which makes "no listview modification" false.
- `Keymap::bind` REFUSES duplicates, and the refresh path re-opens
the panel --- so a naive `keys` implementation would have failed on
every successful refresh.
Two git exit states were measured, not assumed. `--no-index` implies
`--exit-code`, so an untracked diff exits 1 ON SUCCESS --- under the
first predicate, every untracked diff would have rendered a failure row
instead of the diff it had just produced. And `git diff HEAD` exits 128
in an unborn repository, which is exactly a fresh `git init` with the
first files staged.
The unborn policy was then enumerated from a real unborn repository
rather than reasoned about, which closed one case by RULING IT OUT: a
`git mv` of a staged-but-uncommitted file emits `1 A.`, never a `2`
record, so rename/copy is unreachable without a HEAD and needs no
policy. `AM` and `AD` are ordinary there and carry both states, so they
render TWO labelled patches --- `--cached` alone loses the worktree
edit, plain `git diff` alone loses the staged base. The split is
unborn-only: with a HEAD, one total is the question this lane asks.
Section 9 impact is recorded as NEGATIVE and not dressed up: spawned
processes do not appear in `*workers*`, so this adds a fifth
unattributable background thing. The process is labelled; a label is
not attribution.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
parent
4bc55e8dd2
commit
9567c0e09e
|
|
@ -265,6 +265,43 @@ also removed: this branch's "R8 NEEDS A LANE" investigation block, and
|
||||||
durable facts are in the retired registry row and the handoff §6
|
durable facts are in the retired registry row and the handoff §6
|
||||||
census.
|
census.
|
||||||
|
|
||||||
|
## Git integration Stage 1 — BRANCHED, pre-implementation
|
||||||
|
|
||||||
|
**Written with the lane's first commit, before the PR exists** — the
|
||||||
|
standing correction from #171 and #215. This session it was missed on
|
||||||
|
#224 and again on #225, both caught by review; writing it now is the
|
||||||
|
only thing that stops a third.
|
||||||
|
|
||||||
|
**Branch `git-status-stage1`**, base `githubsucks/main` @ `4bc55e8`
|
||||||
|
(the #225 merge). **`githubsucks/git-status-stage1` is the
|
||||||
|
authoritative tip** — the ref, not a SHA. Recover with
|
||||||
|
`git fetch githubsucks && git checkout git-status-stage1`.
|
||||||
|
|
||||||
|
- **Framing `docs/git-integration-framing.md`, revision 5, APPROVED
|
||||||
|
2026-08-09** after four review rounds. Every round found something
|
||||||
|
the previous one had asserted without reading; the doc records which.
|
||||||
|
- **Scope:** `*git-status*` (a `listview` panel over
|
||||||
|
`git --no-optional-locks -C <dir> status --porcelain=v2 --branch -z`)
|
||||||
|
and `*git-diff*` (plain generated text, file-level, no hunk model).
|
||||||
|
Plus **one additive `listview` change**: an optional `keys` table,
|
||||||
|
install-once with match-on-reopen, because `Keymap::bind` refuses
|
||||||
|
duplicates and the refresh path re-opens.
|
||||||
|
- **NO WIRE CHANGE**, and that is load-bearing for scheduling:
|
||||||
|
`PROTOCOL_VERSION` is a strict serialization point, so this lane can
|
||||||
|
run concurrently with other work. **Stage 2 (gutter markers) needs
|
||||||
|
new `DecorationKind` variants and must be scheduled alone.**
|
||||||
|
- **Known negative coherence impact (§9):** git runs as a spawned
|
||||||
|
process, and spawned processes do not appear in `*workers*` — that is
|
||||||
|
`async.lua`'s job list. This adds a fifth unattributable background
|
||||||
|
thing. Labelled honestly; a label is not attribution.
|
||||||
|
- **Verification plan** in framing §6. The load-bearing cases: an `AM`
|
||||||
|
unborn fixture (two labelled patches), untracked diff rendering on
|
||||||
|
**exit 1** (`--no-index` implies `--exit-code`), two successive
|
||||||
|
refreshes not raising `DuplicateBinding`, and a non-UTF-8 path that
|
||||||
|
parses and displays but **refuses** its gestures at the
|
||||||
|
`String`-typed binding boundary.
|
||||||
|
- **Gates:** `scripts/gate --acceptance <the new suite>`.
|
||||||
|
|
||||||
## QoL arc retirement — PR #224 OPEN (docs only)
|
## QoL arc retirement — PR #224 OPEN (docs only)
|
||||||
|
|
||||||
**PR #224** — https://github.com/levineuwirth/pmacs/pull/224. Written
|
**PR #224** — https://github.com/levineuwirth/pmacs/pull/224. Written
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,684 @@
|
||||||
|
# Git integration — Stage 1: seeing what changed
|
||||||
|
|
||||||
|
**Status: framing pass, revision 5. Pre-implementation. Awaiting
|
||||||
|
approval.**
|
||||||
|
|
||||||
|
**Revision 5 completes the unborn-repository policy, which revision 4
|
||||||
|
wrote as three disjoint rows when a single file can be in two states at
|
||||||
|
once.** `AM` — staged, then edited again — is not exotic; it is what a
|
||||||
|
first commit looks like halfway through. The states below were
|
||||||
|
**enumerated from a real unborn repository**, not reasoned about, and
|
||||||
|
one of them settles a case by ruling it out entirely.
|
||||||
|
|
||||||
|
**Revision 4 fixes two contracts that would have failed in ordinary
|
||||||
|
use, both verified against real behaviour rather than reasoned about:**
|
||||||
|
re-binding `d` on every refresh (keymap binds refuse duplicates, so
|
||||||
|
*every successful refresh* would have errored), and two git exit states
|
||||||
|
the failure predicate got wrong. Measured, not assumed — the exit codes
|
||||||
|
below were produced in a scratch repository.
|
||||||
|
|
||||||
|
**Revision 3 pins four Stage 1 contracts revision 2 left loose, and two
|
||||||
|
of those were again claims I made without reading the code I was
|
||||||
|
crediting.** I attributed selection preservation to `listview` and
|
||||||
|
`d` to its key surface; neither is true, and both were checkable in the
|
||||||
|
file I had already cited. The pattern is worth naming since it has now
|
||||||
|
recurred across three revisions: **I cite a file, then describe what I
|
||||||
|
expect it to contain.**
|
||||||
|
|
||||||
|
**Revision 2 answers four blockers, two of which were factual errors in
|
||||||
|
revision 1 that scouting should have caught and did not.** I read
|
||||||
|
`ProjectKind::Git`'s name instead of its doc comment, and I quoted
|
||||||
|
`COHERENCE.md` §15's "no Git integration anywhere in the tree" without
|
||||||
|
checking whether it was still true of the tree. It is not.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Why this, and why now
|
||||||
|
|
||||||
|
`COHERENCE.md` §15 is blunt about it:
|
||||||
|
|
||||||
|
> **There is no Git integration at all** — no status, stage, diff,
|
||||||
|
> blame, or gutter markers anywhere in the tree (gutter git riders and
|
||||||
|
> the `ResourceOffer` diff/blame family are named deferrals). The Git
|
||||||
|
> affordance list above has nothing to attach to yet.
|
||||||
|
|
||||||
|
**That sentence is literally false about the tree, and revision 1
|
||||||
|
repeated it without checking.** `tests/fixtures/pmacs-magit/` is a
|
||||||
|
tracked, installable package — 1,914 lines across four modules, with
|
||||||
|
`status.lua` spawning git through `pmacs.process.spawn` and parsing
|
||||||
|
**`--porcelain=v2 --branch`** into structured sections, plus a 662-line
|
||||||
|
acceptance suite (`tests/m8_6_acceptance.rs`, 32 tests) covering
|
||||||
|
status, refresh, staging, commit, push and branch behaviour.
|
||||||
|
|
||||||
|
**The PRODUCT gap is real and unchanged** — none of that is bundled
|
||||||
|
runtime, so a user who installs pmacs gets no git integration. But
|
||||||
|
"nothing to attach to" understates what exists to *learn from*, and
|
||||||
|
§15's wording should be corrected when this lands.
|
||||||
|
|
||||||
|
For a **daily driver**, this is the largest remaining gap. Not because
|
||||||
|
git is the most architecturally interesting thing missing — §7
|
||||||
|
workspaces and §9 worker identity are both deeper — but because it is
|
||||||
|
the one a user touches *every working hour*, and pmacs currently makes
|
||||||
|
them leave the editor to answer "what have I changed?".
|
||||||
|
|
||||||
|
That is the criterion this lane is chosen against: **frequency of use
|
||||||
|
per day**, not depth of model.
|
||||||
|
|
||||||
|
## 2. Ground truth — what already exists
|
||||||
|
|
||||||
|
Scouted, not assumed:
|
||||||
|
|
||||||
|
- **`ProjectKind::Git` is NOT general repository detection**, and
|
||||||
|
revision 1 said it was. Its doc comment is explicit: *"A bare git
|
||||||
|
repository (no language marker found inside)"* (`src/project.rs:89`).
|
||||||
|
Markers are ordered and a language marker beside `.git` **wins**
|
||||||
|
(`src/project.rs:10`), so a normal Rust repository reports
|
||||||
|
`kind = "rust"` and would have been invisible to a lane that gated on
|
||||||
|
`kind == "git"`. That gate would have failed on this very repository.
|
||||||
|
|
||||||
|
**The rule this lane uses instead: never ask pmacs whether it is a
|
||||||
|
git repo.** Run git in the **active file's directory** and let git
|
||||||
|
resolve its own worktree — `git -C <dir> rev-parse --show-toplevel`
|
||||||
|
establishes the root, and a non-zero exit *is* the "not a repository"
|
||||||
|
answer. Git's own resolution handles submodules, worktrees, `GIT_DIR`
|
||||||
|
and `.git` files; a marker walk reimplements a subset of that and
|
||||||
|
gets it subtly wrong.
|
||||||
|
- **`pmacs.process.spawn` / `events_take` / `terminate` / `forget`**
|
||||||
|
is the working model for running an external tool asynchronously;
|
||||||
|
`builtin/runtime/compile.lua` is a full worked example, including
|
||||||
|
spawn-failure handling and exit markers.
|
||||||
|
- **`pmacs.listview.open`** is a real primitive with existing adopters
|
||||||
|
(`*references*`, `*lsp*`), carrying optional `depth`/`id`,
|
||||||
|
primitive-owned collapse, and selection re-seated by id. `COHERENCE.md`
|
||||||
|
P5 says the remaining work there is **adoption, not construction** —
|
||||||
|
a `*git-status*` panel is exactly that.
|
||||||
|
- **Gutter signs exist in both frontends** — the TUI's leading-column
|
||||||
|
glyph (`src/diag.rs`) and the GPU's `GUTTER_SIGN_X` bars
|
||||||
|
(`pmacs-gpu/src/main.rs:420`).
|
||||||
|
- **A tested porcelain-v2 parser exists as a package fixture** (above).
|
||||||
|
Its `status.lua` deliberately separates **pure `parse_*` functions
|
||||||
|
that take a string and return structure** from the spawning around
|
||||||
|
them — which is the shape that makes a parser testable without a
|
||||||
|
repository, and it is already proven by 32 tests.
|
||||||
|
|
||||||
|
And the constraint that shapes the staging:
|
||||||
|
|
||||||
|
- **`DecorationKind` is a CLOSED enum on the wire**
|
||||||
|
(`pmacs-protocol/src/message.rs:1472`): four diagnostic severities,
|
||||||
|
`Selection`, `SearchMatch`, `SearchMatchActive`, `CurrentLine`.
|
||||||
|
**Gutter markers for git hunks therefore require new variants, which
|
||||||
|
is a protocol version bump.** The gutter signs that exist are keyed
|
||||||
|
on `diagnostic_severity_rank` and have no notion of anything else.
|
||||||
|
|
||||||
|
## 3. The staging, and why the line falls where it does
|
||||||
|
|
||||||
|
**Stage 1 (this lane): read-only, panel-based, NO WIRE CHANGE.**
|
||||||
|
|
||||||
|
- `*git-status*` — a `listview` panel over
|
||||||
|
`git status --porcelain=v2 --branch -z` (Q#G-6), rows visiting the
|
||||||
|
file at RET, refreshed by `g` under the completion model in Q#G-1.
|
||||||
|
- `*git-diff*` — the diff for the **file** under point (Q#G-7), in a
|
||||||
|
generated buffer rendered as **plain text** (no `diff` grammar
|
||||||
|
exists). **No hunk model** — hunks are Stage 2's concern.
|
||||||
|
|
||||||
|
**Stage 2 (separate lane): gutter markers.** Needs new
|
||||||
|
`DecorationKind` variants and a `PROTOCOL_VERSION` bump, plus both
|
||||||
|
frontends' gutter renderers learning a second rider family.
|
||||||
|
|
||||||
|
**Stage 3+ (unscheduled): staging, commit, blame.** Staging and commit
|
||||||
|
are where an editor becomes a git *client*; blame is a lower-frequency
|
||||||
|
read. Neither belongs in front of the two above.
|
||||||
|
|
||||||
|
**The line is drawn at the wire on purpose, and it is a scheduling
|
||||||
|
decision as much as a design one.** Parallel lanes are about to start,
|
||||||
|
and `PROTOCOL_VERSION` is a strict serialization point — two lanes
|
||||||
|
bumping it collide, and this session already recorded what that costs
|
||||||
|
(eight broken version assertions on CI from a single bump). Stage 1
|
||||||
|
touching no wire is what lets it run **concurrently** with other work.
|
||||||
|
Stage 2 must be scheduled alone.
|
||||||
|
|
||||||
|
## 4. Coherence impact (§20)
|
||||||
|
|
||||||
|
Required by `CLAUDE.md` for coherence-affecting work, and this is
|
||||||
|
coherence-affecting — it is §15's named gap.
|
||||||
|
|
||||||
|
- **Journey steps touched:** none directly. Git is not currently a
|
||||||
|
journey step; the golden journey runs open → edit → build → test →
|
||||||
|
navigate. This lane does **not** add a step, and I would rather say
|
||||||
|
so than inflate the claim.
|
||||||
|
- **§15 contextual affordances — the direct target.** The audit's git
|
||||||
|
affordance list ("a Git change stage/revert/diff") has *nothing to
|
||||||
|
attach to*. Stage 1 creates the thing to attach to; the affordances
|
||||||
|
themselves follow it, and the menu's context vocabulary
|
||||||
|
(`src/menu.rs:44`) would need a `git` context to host them — **out of
|
||||||
|
scope here**, named so it is not forgotten.
|
||||||
|
- **§14 workbench primitives — adoption, which is the stated need.**
|
||||||
|
`*git-status*` becomes the **fifth** `listview` call site and the
|
||||||
|
first outside the LSP panels, which is the concrete evidence P5 asks
|
||||||
|
for that the primitive generalizes past its first consumer.
|
||||||
|
- **Interaction islands (§6): none added, and this is a real
|
||||||
|
constraint.** The panel gets no hardcoded key interception; it uses
|
||||||
|
`listview`'s existing key handling. §6 records six such shadows and
|
||||||
|
calls them "weak, and growing" — this lane must not make it seven.
|
||||||
|
- **Config registry adoption:** at least one setting
|
||||||
|
(`git.enabled`, Q#G-4), defined through `pmacs.config.define` like
|
||||||
|
`ui.line-wrap` and the zoom settings, not a bare Lua global.
|
||||||
|
- **Background-work attribution (§9): NEGATIVE, and named as such.**
|
||||||
|
Git runs as a spawned process, and spawned processes do **not** appear
|
||||||
|
in `*workers*` — that view is `async.lua`'s job list; processes live
|
||||||
|
under `pmacs.process.list` (Q#G-5). This lane therefore adds a fifth
|
||||||
|
thing running in the background with no single place to see it. The
|
||||||
|
process is labelled honestly, which is better than anonymous, but
|
||||||
|
**a label is not attribution and this document does not pretend
|
||||||
|
otherwise.** Accepted because these are short-lived reads; it would
|
||||||
|
not be acceptable for Stage 3's push/pull.
|
||||||
|
|
||||||
|
## 5. Open questions
|
||||||
|
|
||||||
|
### Q#G-1 — is the status panel a snapshot or a live view?
|
||||||
|
|
||||||
|
A snapshot is a command that opens a panel; a live view refreshes on
|
||||||
|
buffer save, on focus, or on a filesystem watch.
|
||||||
|
|
||||||
|
*My vote: **snapshot, refreshed explicitly***, with `g` re-running
|
||||||
|
inside the panel. Live refresh needs a watch mechanism, an invalidation
|
||||||
|
rule, and a §9 story for the recurring work — all real arcs. A snapshot
|
||||||
|
is honest, useful the first day, and does not pretend to a currency it
|
||||||
|
cannot maintain.
|
||||||
|
|
||||||
|
**But "explicit refresh" does not fit `listview` unmodified, and
|
||||||
|
revision 1 missed that.** `listview.refresh` is synchronous:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local rows = check_ids(p.on_refresh() or {}) -- listview.lua:402
|
||||||
|
```
|
||||||
|
|
||||||
|
The result is consumed immediately. `pmacs.process.spawn` cannot return
|
||||||
|
rows there — it returns a process id whose output is drained later. So
|
||||||
|
revision 1's "adopt `listview`" would have produced exactly one of the
|
||||||
|
two failures the reviewer named: a reimplemented list, or a `g` that
|
||||||
|
silently does nothing. The primitive's own docs already call a dead `g`
|
||||||
|
out as a defect it must not repeat (`listview.lua:416`).
|
||||||
|
|
||||||
|
**The completion model, specified.** `on_refresh` stays synchronous and
|
||||||
|
honest:
|
||||||
|
|
||||||
|
1. **`on_refresh` returns the CURRENT rows immediately**, with a
|
||||||
|
`refreshing…` marker row appended, and *kicks off* the spawn. `g` is
|
||||||
|
therefore never a no-op — it always re-renders and always shows that
|
||||||
|
work started.
|
||||||
|
2. **On exit, the completion handler re-opens the panel** via
|
||||||
|
`listview.open` with the same `name` — **and re-seats the selection
|
||||||
|
itself.**
|
||||||
|
|
||||||
|
Revision 2 credited that to the primitive and was wrong.
|
||||||
|
`listview.open` **resets collapse** (`p.collapsed = {}`) and
|
||||||
|
**always seats line 1** (`seat_cursor(p, 1)`,
|
||||||
|
`builtin/runtime/listview.lua:337-378`). The `listview.lua:82` note
|
||||||
|
I cited is about **name** disambiguation to `<2>`, not selection.
|
||||||
|
Only `listview.refresh` preserves a selection, and that is the
|
||||||
|
synchronous path this model cannot use.
|
||||||
|
|
||||||
|
So the contract is explicit and owned here: **capture the selected
|
||||||
|
row's git id (its current path) before re-opening, and after
|
||||||
|
re-opening move to the line whose row carries that id**, computed
|
||||||
|
from the handler's own rows array via `pmacs.editor.move_to_line`.
|
||||||
|
If the id is gone from the new status — the commonest case, since a
|
||||||
|
file that stopped being modified drops out — seat line 1 and say
|
||||||
|
nothing; that is the correct answer, not a failure.
|
||||||
|
|
||||||
|
**Collapse state is moot in Stage 1** because the rows are flat: no
|
||||||
|
`depth`, so nothing to collapse. Stage 2 or a sectioned view would
|
||||||
|
have to revisit this, and would then face the same reset.
|
||||||
|
3. **Concurrent refresh is suppressed by a generation counter.** A
|
||||||
|
second `g` while one is in flight bumps the generation; the older
|
||||||
|
completion sees a stale generation and **discards its rows** rather
|
||||||
|
than racing. It does not terminate the first process — reaping is
|
||||||
|
`pmacs.process.forget`'s job and killing git mid-read buys nothing.
|
||||||
|
4. **Failure is a row, not a silence.** Non-zero exit or spawn failure
|
||||||
|
renders a row carrying the exit code and the first stderr line, plus
|
||||||
|
a status message. §1.2's silence asymmetry.
|
||||||
|
5. **Panel lifetime.** If the panel's buffer is gone when the process
|
||||||
|
exits, the handler drops the result. `compile.lua:252` already
|
||||||
|
handles the buffer-killed case for its own slot; the same shape.
|
||||||
|
|
||||||
|
**The alternative — extending `listview` with an async contract — is
|
||||||
|
the more correct long-term answer** and is deliberately not taken here:
|
||||||
|
it changes a primitive with four existing adopters, and doing that from
|
||||||
|
inside its fifth adopter's lane is how a primitive acquires a consumer's
|
||||||
|
idiosyncrasies. **If review prefers it, it belongs in its own lane
|
||||||
|
before this one.**
|
||||||
|
|
||||||
|
### Q#G-0 — what is the relationship to `pmacs-magit`? **(new in rev 2)**
|
||||||
|
|
||||||
|
The reviewer's framing of the choice is right: adopt, replace, or
|
||||||
|
declare it out-of-product precedent. Doing none of those and quietly
|
||||||
|
writing a second parser is the option that must not happen.
|
||||||
|
|
||||||
|
*My vote: **port its pure `parse_*` functions and its test corpus into
|
||||||
|
the bundled runtime; leave the fixture itself untouched.***
|
||||||
|
|
||||||
|
- **The record TOKENIZER is deliberately rewritten, not ported.** The
|
||||||
|
fixture parses **newline-delimited** v2; Stage 1 reads **`-z`**, and
|
||||||
|
those are different grammars — under `-z` a record's fields are
|
||||||
|
NUL-terminated and a rename carries its two paths as separate fields
|
||||||
|
rather than tab-joined. Saying "port the parser" would have been
|
||||||
|
wrong; what ports is the **separation** (pure `parse_*` functions
|
||||||
|
over a string, testable with no repository) and the **case coverage**
|
||||||
|
its 32 tests encode. The tokenizer underneath is new, and its
|
||||||
|
correctness rests on this lane's own corpus.
|
||||||
|
- **Port, not import.** The fixture's purpose is to prove the *package
|
||||||
|
system* can host this. If bundled code became its dependency, it
|
||||||
|
would stop demonstrating an independent package and `m8_6` would test
|
||||||
|
less than it claims.
|
||||||
|
- **The duplication is therefore deliberate**, and it is the one place
|
||||||
|
this framing accepts two copies of a rule after a session spent
|
||||||
|
removing them. The justification is that they answer different
|
||||||
|
questions — one is product behaviour, one is package-system
|
||||||
|
capability — and coupling them weakens the second. **If review
|
||||||
|
prefers the coupling, that is a defensible call and I will take it**;
|
||||||
|
what I will not do is leave the duplication unstated.
|
||||||
|
- **It also settles Q#G-2's format**: the existing, tested parser is
|
||||||
|
**porcelain v2**, so Stage 1 is v2. Revision 1 said v1 for no reason
|
||||||
|
beyond familiarity.
|
||||||
|
|
||||||
|
### Q#G-2 — `git` the binary, or a library?
|
||||||
|
|
||||||
|
*My vote: **the binary**, via `pmacs.process.spawn`. `compile.lua` is
|
||||||
|
the worked precedent, the daemon already spawns external tools, and a
|
||||||
|
git library is a dependency with a much larger surface than "run one
|
||||||
|
command and parse porcelain". `--porcelain=v2` is explicitly a stable
|
||||||
|
machine format; that is what it is for.
|
||||||
|
|
||||||
|
**Named risk:** no `git` on `PATH`. §1.2's *silence asymmetry* says the
|
||||||
|
failure must be **surfaced with guidance**, not swallowed — the same
|
||||||
|
lesson #204 landed for a missing language server.
|
||||||
|
|
||||||
|
### Q#G-6 — the status data contract **(new in rev 2)**
|
||||||
|
|
||||||
|
Revision 1 said "`--porcelain=v1`" and proposed "a path with a space"
|
||||||
|
as the parsing witness. **Both were inadequate.** Porcelain without
|
||||||
|
`-z` emits paths in git's **C quoting** for anything non-ASCII or
|
||||||
|
containing special characters, and rename/copy records carry *two*
|
||||||
|
paths whose separation is positional. A single space-in-path fixture
|
||||||
|
proves none of that.
|
||||||
|
|
||||||
|
*My vote: the exact invocation*
|
||||||
|
|
||||||
|
```
|
||||||
|
git --no-optional-locks -C <dir> status --porcelain=v2 --branch -z
|
||||||
|
```
|
||||||
|
|
||||||
|
**`--no-optional-locks` is part of the contract, not a nicety.**
|
||||||
|
`git status` is **not strictly read-only**: it may refresh and write
|
||||||
|
the index, and git's own documentation recommends this flag for
|
||||||
|
background scripts precisely so a background reader does not contend
|
||||||
|
for `index.lock` with the user's real git commands
|
||||||
|
(<https://git-scm.com/docs/git-status>). This lane runs status
|
||||||
|
*asynchronously, from an editor, while the user may be running git in a
|
||||||
|
terminal* — the exact scenario the flag exists for. Revision 2 called
|
||||||
|
the lane "read-only" and that was wrong about the mechanism.
|
||||||
|
|
||||||
|
It is **witnessed structurally** — the assembled argv is asserted to
|
||||||
|
carry the flag — because observing a lock that was *not* taken is not
|
||||||
|
something a test can do directly. Verified accepted by the git in use
|
||||||
|
here.
|
||||||
|
|
||||||
|
The rest: `--porcelain=v2 --branch -z`, also verified accepted. NUL delimiting removes C quoting from
|
||||||
|
the problem **entirely** rather than obliging a hand-written unquoter,
|
||||||
|
and it makes the two-path rename record unambiguous: the paths are
|
||||||
|
separate NUL-terminated fields rather than tab-joined inside one.
|
||||||
|
|
||||||
|
The rename/copy identity rule to pin: a `2` record carries the current
|
||||||
|
path **and** its origin, and the panel must show which file it is now
|
||||||
|
while remembering where it came from — a row whose id is the current
|
||||||
|
path, since that is what RET visits.
|
||||||
|
|
||||||
|
**Witness corpus, not one case:** modified, added, deleted, untracked,
|
||||||
|
**renamed (both paths)**, **copied**, a path with a space, a path with
|
||||||
|
a newline, and a non-UTF-8 path. The last two are exactly what `-z`
|
||||||
|
buys and what a quoted parser gets wrong.
|
||||||
|
|
||||||
|
### Q#G-7 — the diff gesture **(new in rev 2)**
|
||||||
|
|
||||||
|
Revision 1 wrote "the diff for the file or hunk under point" while also
|
||||||
|
committing RET to visiting the file. **RET cannot do both, there is no
|
||||||
|
second binding proposed, and no hunk model exists anywhere in the
|
||||||
|
tree.**
|
||||||
|
|
||||||
|
*My vote:*
|
||||||
|
|
||||||
|
- **RET visits the file** — unchanged, and the behaviour a list of
|
||||||
|
files should have.
|
||||||
|
- **A named command, `git.diff-file`, bound to `d` inside the panel.**
|
||||||
|
|
||||||
|
**`d` is not on `listview`'s key surface**, and revision 2 said it
|
||||||
|
was. The bound set is exactly `RET SPC n <down> p <up> TAB g q`
|
||||||
|
(`builtin/runtime/listview.lua:266-279`), bound buffer-locally inside
|
||||||
|
the primitive, which is the only place the panel's buffer handle is
|
||||||
|
known. **Looking the buffer up by name from outside is unsafe** —
|
||||||
|
`listview` deliberately disambiguates a collision to `<2>`, so the
|
||||||
|
name a consumer passed is not necessarily the buffer it got.
|
||||||
|
|
||||||
|
*My vote: **a `keys` table on the open spec***, e.g.
|
||||||
|
`keys = { d = "git.diff-file" }`, bound through the same
|
||||||
|
`bind_local_keymap` that already binds the fixed set. It is additive,
|
||||||
|
general to any adopter, keeps binding where the buffer is known, and
|
||||||
|
adds **no** interception — the §6 constraint holds.
|
||||||
|
|
||||||
|
**The registration lifecycle, which revision 3 omitted and which
|
||||||
|
would have broken the refresh path it depends on.** `Keymap::bind`
|
||||||
|
**refuses duplicates** — `KeymapError::DuplicateBinding`, *"Refuse
|
||||||
|
rather than silently overwrite"* (`src/keymap_tree.rs:75`) — and the
|
||||||
|
completion model calls `listview.open` again on **every** refresh. A
|
||||||
|
naive `keys` implementation therefore errors on the second open, so
|
||||||
|
**every successful refresh would have failed while re-binding `d`.**
|
||||||
|
|
||||||
|
The contract:
|
||||||
|
|
||||||
|
1. **Keys are installed once, when the panel's buffer is created**,
|
||||||
|
and stored on the panel.
|
||||||
|
2. **A later `open` for a live panel does not re-bind.** It
|
||||||
|
**compares** the supplied `keys` against the stored table and
|
||||||
|
**errors on divergence** rather than ignoring it. Silently keeping
|
||||||
|
the old binding would give the consumer a key that does something
|
||||||
|
other than what it just asked for — a dead or lying key, which is
|
||||||
|
the defect `listview` already condemns for `g`.
|
||||||
|
3. **Collisions are rejected at install time**, against both the
|
||||||
|
fixed set (`RET SPC n <down> p <up> TAB g q`) and any
|
||||||
|
**prefix conflict** — `Keymap` has a separate error for turning a
|
||||||
|
leaf into a submap, and a `keys` table must not be able to reach
|
||||||
|
it.
|
||||||
|
|
||||||
|
(The alternative, idempotent re-registration, is tolerable but
|
||||||
|
strictly weaker: it makes a consumer that changes its keys mid-session
|
||||||
|
silently wrong instead of loudly wrong.)
|
||||||
|
|
||||||
|
**This IS a `listview` modification, and revision 2's "no listview
|
||||||
|
modification" was false.** I distinguish it from the async-contract
|
||||||
|
change I deferred: that one alters *when* an existing callback's
|
||||||
|
result is consumed for four existing adopters; this adds an optional
|
||||||
|
field that changes nothing for a spec that omits it. **If review
|
||||||
|
judges any primitive change out of an adopter's lane, the alternative
|
||||||
|
is `listview.open` returning the panel buffer** so the consumer binds
|
||||||
|
its own key — smaller still, but it pushes binding to every adopter.
|
||||||
|
- **No hunk model in Stage 1.** Hunks are precisely what gutter markers
|
||||||
|
need, and that is Stage 2's protocol work. Introducing a half hunk
|
||||||
|
model here to serve one gesture would prejudge Stage 2's design from
|
||||||
|
the wrong side.
|
||||||
|
|
||||||
|
**And what `d` actually SHOWS, which revision 2 left unstated.** "File,
|
||||||
|
not hunk" is a scope, not a contract. A porcelain-v2 row carries an
|
||||||
|
**XY** pair — X staged (index vs HEAD), Y unstaged (worktree vs index)
|
||||||
|
— and the three plausible diffs answer three different questions:
|
||||||
|
`git diff` shows only Y, `--cached` only X, and neither shows an
|
||||||
|
untracked file at all.
|
||||||
|
|
||||||
|
*My vote: **`d` answers the lane's own question — "what have I
|
||||||
|
changed?" — against `HEAD`:***
|
||||||
|
|
||||||
|
| row | `d` runs | why |
|
||||||
|
|---|---|---|
|
||||||
|
| staged, unstaged, or both | `git diff HEAD -- <path>` | one view of the total change; splitting X from Y is a staging UI, which is Stage 3 |
|
||||||
|
| deleted | `git diff HEAD -- <path>` | shows the deletion; no special case needed |
|
||||||
|
| renamed / copied | `git diff HEAD -- <orig> <current>` | v2 gives both paths; passing both is what lets rename detection render it as a rename rather than an unrelated add+delete |
|
||||||
|
| **untracked** | `git diff --no-index -- /dev/null <path>` | **a normal diff shows nothing at all** for an untracked file. Without this case `d` is silently dead on the rows a user is most likely to press it on |
|
||||||
|
| non-UTF-8 path | *refuses, with a message* | see Q#G-8 |
|
||||||
|
|
||||||
|
The `HEAD` choice is deliberate and is the one thing here I would most
|
||||||
|
expect review to push back on: it is the right default for *reading*
|
||||||
|
what changed, and the wrong one for *staging*, which is why it is
|
||||||
|
correct for Stage 1 and will need revisiting when Stage 3 arrives.
|
||||||
|
|
||||||
|
**The exit-state contract, which revision 3 got wrong in two ways.**
|
||||||
|
"Non-zero exit renders a failure row" is not correct for `git diff`.
|
||||||
|
Both cases below were measured in a scratch repository, not inferred:
|
||||||
|
|
||||||
|
**(a) `--no-index` implies `--exit-code`.** It exits **1 when it
|
||||||
|
successfully finds differences** — measured: `exit=1` for an untracked
|
||||||
|
file against `/dev/null`. Under revision 3's predicate, *every*
|
||||||
|
untracked diff — the case `--no-index` exists to serve — would have
|
||||||
|
rendered a failure row instead of the diff it just produced.
|
||||||
|
|
||||||
|
So for the untracked path the success predicate is **exit ∈ {0, 1}**,
|
||||||
|
rendering whatever came out; **exit ≥ 2 is a real failure**. That
|
||||||
|
asymmetry is confined to the `--no-index` invocation and does not leak
|
||||||
|
to the others, where non-zero still means failure.
|
||||||
|
|
||||||
|
**(b) An unborn repository has no `HEAD`.** Measured:
|
||||||
|
`git diff HEAD -- <path>` exits **128** with `fatal: bad revision
|
||||||
|
'HEAD'`. This is not an edge case — it is a freshly `git init`-ed
|
||||||
|
repository with the first files staged, which is exactly when someone
|
||||||
|
opens a status panel to see what they are about to commit.
|
||||||
|
|
||||||
|
*Policy: **detect once, then split**.*
|
||||||
|
|
||||||
|
**Detection needs no extra subprocess.** `--branch` already reports
|
||||||
|
`# branch.oid (initial)` when `HEAD` is unborn — observed in the
|
||||||
|
output this lane already parses. Revision 4 proposed a separate
|
||||||
|
`git rev-parse --verify --quiet HEAD`; that is a second process for a
|
||||||
|
fact the first one hands over.
|
||||||
|
|
||||||
|
**The reachable states, enumerated from a real unborn repository** —
|
||||||
|
`git init`, stage three files, then edit one, delete one, and `git mv`
|
||||||
|
one:
|
||||||
|
|
||||||
|
```
|
||||||
|
# branch.oid (initial)
|
||||||
|
1 AD ... ad.txt
|
||||||
|
1 AM ... am.txt
|
||||||
|
1 A. ... r_new.txt <- the `git mv`
|
||||||
|
? untracked.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Two findings fall straight out:
|
||||||
|
|
||||||
|
- **`AM` and `AD` are ordinary and carry BOTH states**, which is
|
||||||
|
exactly the gap: `--cached` alone loses the worktree delta, plain
|
||||||
|
`git diff` alone loses the staged base.
|
||||||
|
- **Rename and copy CANNOT occur under an unborn `HEAD`.** The
|
||||||
|
`git mv` produced `1 A. … r_new.txt` — an ordinary add of the new
|
||||||
|
path, **not** a `2` record. With no `HEAD` there is nothing to
|
||||||
|
rename *from*, so the rename/copy row class is unreachable here and
|
||||||
|
needs no unborn policy. That is a case closed by evidence rather than
|
||||||
|
handled speculatively.
|
||||||
|
|
||||||
|
| unborn row | `d` renders |
|
||||||
|
|---|---|
|
||||||
|
| `A.` staged only | one patch: `git diff --cached -- <path>` |
|
||||||
|
| **`AM` staged + edited** | **two labelled patches** — *staged* `git diff --cached -- <path>`, then *unstaged* `git diff -- <path>` |
|
||||||
|
| **`AD` staged + deleted** | **two labelled patches**, same pair; the second renders the deletion |
|
||||||
|
| `.M` / `.D` unstaged only | one patch: `git diff -- <path>` |
|
||||||
|
| `?` untracked | `git diff --no-index -- /dev/null <path>` (exit ∈ {0,1}) |
|
||||||
|
| rename / copy | **unreachable** — see above |
|
||||||
|
|
||||||
|
All four `--cached` / plain invocations above were run against that
|
||||||
|
repository and render the expected patches.
|
||||||
|
|
||||||
|
**The split is unborn-only, and that asymmetry is deliberate.** Once
|
||||||
|
`HEAD` exists, `git diff HEAD` gives one total — which is the lane's
|
||||||
|
question — and splitting it would be a staging UI (Stage 3). The split
|
||||||
|
appears here only because there is no `HEAD` to total *against*.
|
||||||
|
|
||||||
|
The generated buffer carries a **header naming what it is showing**:
|
||||||
|
*"no commits yet — split view: staged (index) above, unstaged
|
||||||
|
(worktree) below"*. Revision 4's wording ("showing staged changes")
|
||||||
|
would have described a single total-against-`HEAD` diff, which is
|
||||||
|
precisely what this is not. A diff that silently answers a different
|
||||||
|
question than the one asked is worse than one that says so — and a
|
||||||
|
header that misdescribes a split view is the same failure in smaller
|
||||||
|
type.
|
||||||
|
|
||||||
|
### Q#G-8 — non-UTF-8 paths: an honest boundary **(new in rev 3)**
|
||||||
|
|
||||||
|
Revision 2 listed a non-UTF-8 path in the witness corpus as though it
|
||||||
|
were an end-to-end case. **It cannot be**, and the boundary is in the
|
||||||
|
bindings: `pmacs.process.spawn` takes `args: Vec<String>`
|
||||||
|
(`src/lua_bindings/mod.rs:8683`) and `pmacs.buffer.find_or_open` takes
|
||||||
|
`path: String` (`:3564`). Both are Rust `String`, i.e. UTF-8 by
|
||||||
|
construction. A path that is valid bytes but not valid UTF-8 can be
|
||||||
|
*read* from git's `-z` output and *displayed*, but it cannot be passed
|
||||||
|
back to `spawn` for a diff, nor opened.
|
||||||
|
|
||||||
|
*My vote: **parse it, show it, and refuse the gesture with a
|
||||||
|
message***:
|
||||||
|
|
||||||
|
- the row **appears** in the panel, so the user is not lied to about
|
||||||
|
what is modified;
|
||||||
|
- **RET and `d` on that row report** that the path is not representable
|
||||||
|
and do nothing else — a witnessed refusal, not a stack trace or a
|
||||||
|
silent no-op;
|
||||||
|
- **it is removed from the end-to-end promise.** The witness is
|
||||||
|
parser-and-display **plus the refusal**, and the framing does not
|
||||||
|
claim visiting works.
|
||||||
|
|
||||||
|
Making it work end-to-end means `OsString`/bytes through two binding
|
||||||
|
boundaries — a real change to the Lua API surface, and not this lane's.
|
||||||
|
|
||||||
|
### Q#G-3 — what does the diff view render into?
|
||||||
|
|
||||||
|
*My vote: **a generated buffer**, reusing the generated-buffer
|
||||||
|
immutability work (Stage 1 merged; that lane's Stage 2 is queued).
|
||||||
|
Diff output is read-only text and that machinery exists.
|
||||||
|
|
||||||
|
**RESOLVED in rev 2 — there is no bundled `diff` grammar.**
|
||||||
|
`BUILTIN_LANGUAGES` (`src/syntax.rs`) has no `diff` entry; checked, not
|
||||||
|
assumed. **Stage 1 renders plain generated text**, and diff
|
||||||
|
highlighting is later work needing a grammar first.
|
||||||
|
|
||||||
|
### Q#G-4 — what is configurable?
|
||||||
|
|
||||||
|
*My vote: **one setting to start** — `git.enabled` (boolean, default
|
||||||
|
`true`), through the config registry. Resist more until there is use
|
||||||
|
evidence; §11's grade is "partial (foundation only)" and adding five
|
||||||
|
speculative settings is how a registry becomes noise.
|
||||||
|
|
||||||
|
### Q#G-5 — §9 attribution — **RESOLVED, and the answer is negative**
|
||||||
|
|
||||||
|
Revision 1 deferred this to implementation. That was wrong: it is
|
||||||
|
answerable by reading, and deferring it would have meant discovering a
|
||||||
|
known coherence cost *after* committing to the design.
|
||||||
|
|
||||||
|
**A spawned git process does not appear in `*workers*` at all.** That
|
||||||
|
buffer is `builtin/runtime/async.lua`'s (`:490`) and lists **async
|
||||||
|
jobs**; spawned processes live separately under `pmacs.process.list`.
|
||||||
|
They are two of the four disjoint activity views §9 grades as
|
||||||
|
"mechanism without identity".
|
||||||
|
|
||||||
|
So, stated plainly rather than dressed up:
|
||||||
|
|
||||||
|
- **This lane adds a fifth thing that runs in the background and is not
|
||||||
|
attributable from one place.** That is a **negative** coherence impact
|
||||||
|
against §9, and it is the honest cost of shipping git status before
|
||||||
|
worker identity exists.
|
||||||
|
- **Labelling the process is still required** — a clear label under
|
||||||
|
`pmacs.process.list` is strictly better than an anonymous `git`. But
|
||||||
|
**a label does not solve attribution**, and this document does not
|
||||||
|
claim it does. The claim is only: do not make it worse than it has to
|
||||||
|
be.
|
||||||
|
- **The mitigation is bounded in time, not in kind.** These are
|
||||||
|
short-lived reads, not long-running jobs; a `git status` that has not
|
||||||
|
finished is a bug, not a background task a user needs to supervise.
|
||||||
|
That is why the cost is acceptable *now* and would not be for
|
||||||
|
Stage 3's push/pull.
|
||||||
|
|
||||||
|
## 6. Verification
|
||||||
|
|
||||||
|
- **Parsing, against a corpus rather than a case (Q#G-6):** modified,
|
||||||
|
added, deleted, untracked, **renamed with both paths**, **copied**, a
|
||||||
|
path with a space, and **a path with a newline** — the last is what
|
||||||
|
`-z` buys, and a parser that passes only the space case is the one
|
||||||
|
that ships broken.
|
||||||
|
- **A non-UTF-8 path is parsed and displayed, and its gestures refuse
|
||||||
|
with a message** (Q#G-8) — a witnessed refusal at the binding
|
||||||
|
boundary, **not** an end-to-end visit.
|
||||||
|
- **The argv carries `--no-optional-locks`** (Q#G-6), asserted
|
||||||
|
structurally. A lock not taken cannot be observed directly, so the
|
||||||
|
invocation is what gets pinned.
|
||||||
|
- **`d` is witnessed on every row class** (Q#G-7): staged, unstaged,
|
||||||
|
both, deleted, renamed, and **untracked** — the last because a normal
|
||||||
|
`git diff` shows nothing there, so a missing `--no-index` case makes
|
||||||
|
`d` silently dead exactly where it is most used.
|
||||||
|
- **The untracked diff renders on exit 1**, not a failure row (Q#G-7a)
|
||||||
|
— the case `--exit-code` semantics would otherwise break, and the
|
||||||
|
one most likely to be "fixed" later by someone who reads exit 1 as an
|
||||||
|
error.
|
||||||
|
- **An unborn repository is witnessed end to end**, and the fixture is
|
||||||
|
**`AM`** specifically — staged then edited again, the shape a first
|
||||||
|
commit actually has partway through. `git init`, stage, edit, open
|
||||||
|
the panel, press `d`, and get **two labelled patches** with the
|
||||||
|
split-view header — not `fatal: bad revision 'HEAD'`, and not a
|
||||||
|
single `--cached` patch that silently drops the worktree edit.
|
||||||
|
**`AD` rides the same fixture**, since one repository can hold both.
|
||||||
|
- **Unborn detection reads `# branch.oid (initial)`** from the status
|
||||||
|
output already being parsed — asserted, so nobody later reintroduces
|
||||||
|
a second `rev-parse` process for a fact already in hand.
|
||||||
|
- **Rename/copy under an unborn `HEAD` is asserted UNREACHABLE**: the
|
||||||
|
fixture `git mv`s a staged-but-uncommitted file and the parser sees a
|
||||||
|
`1 A.` record, never a `2`. Pinned so a future reader does not
|
||||||
|
"fix" the missing unborn rename policy by inventing one.
|
||||||
|
- **Re-binding across a refresh does not error** (Q#G-7): two
|
||||||
|
successive refreshes on a live panel, asserting `d` still works and
|
||||||
|
no `DuplicateBinding` surfaced. This is the one that would have
|
||||||
|
broken on every refresh.
|
||||||
|
- **A `keys` table colliding with the fixed set is rejected at install
|
||||||
|
time**, as is a prefix conflict.
|
||||||
|
- **Selection is re-seated by the completion handler** (Q#G-1), across
|
||||||
|
a refresh that reorders rows, and **falls back to line 1 without
|
||||||
|
complaint when the selected path drops out of status** — the common
|
||||||
|
case, not an error.
|
||||||
|
- **The pure `parse_*` functions are tested without a repository**,
|
||||||
|
which is the shape `pmacs-magit/status.lua` already proves works and
|
||||||
|
the reason to port that separation rather than invent one.
|
||||||
|
- **A repository fixture built with real `git`**, in a tempdir, and
|
||||||
|
**bounded with `set_search_boundary`** — R8 was retired two commits
|
||||||
|
ago and is precisely what happens when a fixture lets project
|
||||||
|
detection escape into the developer's environment.
|
||||||
|
- **The root rule is witnessed on a repository whose `ProjectKind` is
|
||||||
|
NOT `Git`** — i.e. an ordinary language project with a `.git` beside
|
||||||
|
its manifest. That is the case revision 1's `kind == "git"` gate
|
||||||
|
would have failed, and this repository is one.
|
||||||
|
- **Missing `git` on `PATH` is witnessed**, not assumed (Q#G-2), and
|
||||||
|
surfaces guidance rather than silence.
|
||||||
|
- **`g` is never a no-op** (Q#G-1): it re-renders and marks that work
|
||||||
|
started, even mid-flight. A dead `g` is a defect `listview` already
|
||||||
|
names.
|
||||||
|
- **Concurrent refresh discards the stale generation** rather than
|
||||||
|
racing — asserted by driving two refreshes and completing them out of
|
||||||
|
order.
|
||||||
|
- **Failure renders a row**, carrying exit code and stderr.
|
||||||
|
- **The panel is a `listview` adopter**, asserted structurally, so a
|
||||||
|
future re-implementation of list behaviour inside git code fails the
|
||||||
|
test rather than passing review.
|
||||||
|
- **No new interaction island** — `d` is bound buffer-locally through
|
||||||
|
`listview`'s own binding path (Q#G-7), not a hardcoded interception.
|
||||||
|
§6 stays at six shadows.
|
||||||
|
|
||||||
|
Gates via `scripts/gate --acceptance <the new suite>`.
|
||||||
|
|
||||||
|
**What this will NOT prove:** that background git work is attributable
|
||||||
|
(Q#G-5 — it is not, by construction), or that the parser handles
|
||||||
|
porcelain versions other than v2.
|
||||||
|
|
||||||
|
## 7. Not in scope
|
||||||
|
|
||||||
|
Gutter markers and any `DecorationKind`/`PROTOCOL_VERSION` change
|
||||||
|
(Stage 2 — must be scheduled alone). Staging, commit, push, pull,
|
||||||
|
branch operations, merge-conflict resolution. Blame. A `git` context in
|
||||||
|
the menu vocabulary. Any git *library* dependency. Live refresh
|
||||||
|
(Q#G-1). Fixing §9's worker identity — this lane makes it marginally
|
||||||
|
worse and says so (Q#G-5). Any hunk model (Q#G-7). Modifying the
|
||||||
|
`listview` primitive to carry an async contract — the better long-term
|
||||||
|
answer, but it belongs in its own lane before this one, not inside its
|
||||||
|
fifth adopter (Q#G-1). Changing `tests/fixtures/pmacs-magit/` or
|
||||||
|
`tests/m8_6_acceptance.rs` (Q#G-0).
|
||||||
|
|
||||||
|
**A `listview` change IS in scope after all** (Q#G-7): an optional
|
||||||
|
`keys` table on the open spec. Revision 2 said no primitive
|
||||||
|
modification; that was false, because `d` cannot be bound from outside
|
||||||
|
the primitive safely. The async-contract change stays out.
|
||||||
|
|
||||||
|
**One correction this lane should carry when it lands:** `COHERENCE.md`
|
||||||
|
§15's "no Git integration anywhere in the tree" is literally false —
|
||||||
|
`tests/fixtures/pmacs-magit/` exists. The *product* gap it describes is
|
||||||
|
real; the sentence needs narrowing to say so.
|
||||||
Loading…
Reference in New Issue