~bigbes/sr-ht-spec

ref: a93855e6dc08555784023f290b5eb5ed5f830437 sr-ht-spec/.beads/interactions.jsonl -rw-r--r-- 8.9 KiB
e849de2a — Eugene Blikh 13 days ago
chore(beads): close spec-ejq.2, CI publish is green on build #251
cc90b4a3 — Eugene Blikh 13 days ago
fix(web): a code fence whose language changed says so (spec-by6.4)

prosediff hashes a block's Info, so ```go becoming ```python pairs the two
fences as a modification — but Block.Lines holds a fence's contents without its
delimiters, so the line script came out entirely equal and every row rendered
as context. The page said the document changed and then showed nothing that
had, which is worse than either saying nothing or showing the change: the
reviewer looks for an edit that appears not to exist.

The fence's opening delimiter is not a row of this table and inventing a line
number for it would be a guess, so the change is stated as a marker row above
the fence's lines, in the same shape a move already uses. Only a code fence is
covered — Info also carries a list item's marker and a table's column count,
and neither is a language a reviewer would want announced.
f42f81ca — Eugene Blikh 13 days ago
feat(web): line-numbered unified prose diff replaces the block cards (spec-by6.3.5)

The block-card renderer was reviewed against a live proposal and rejected:
"ADDED PARAGRAPH" outweighed the content on every row, every block carried
identical chrome, and on a new file the whole page is one change, so the cards
added noise and no signal. Two columns of digits say the same thing and then
get out of the way, which is what a gutter is for.

Selection is by line, anchoring is by block. Lines are what the cursor lands
on; block hashes are what survive a reflow. The web layer maps a selected line
range onto its enclosing prosediff block and stores the existing
core.CommentAnchor unchanged — service/, db/ and core/ do not move and the POST
wire format is untouched. The composer states which block it will anchor to
before anything is typed, so the indirection is visible rather than magic.

A line number is never guessed. A modified prose block goes through
prosediff.WordsByLine, whose ok=false contract is honoured with a paired
old/new region stating a line RANGE; a block rewritten past the similarity
threshold takes that path too. An equal block that was rewrapped states an old
number only for the lines the old revision really holds — equal line counts
were the first rule and were not proof, which a property test over 2800
generated document/edit pairs found within seventeen cases.

The markup is a table because prose wraps and a number has to stay on the first
visual line of the line it names. One rail ground behind both number tracks
with a single hairline against the content; the change tint starts at the sign
column so the gutter never reads as part of the change; heading rows pin
themselves as the section readout, replacing the per-hunk breadcrumb that only
restated a heading three rows above.

Folding and commenting both work with JavaScript off — the fold is a checkbox,
and every block keeps a visible composer. With the script in, that per-block
composer is hidden and reached by selecting lines instead, because sixteen
identical "comment on this block" rows are the chrome this port removes. A
composer holding typed text is never hidden by anything.

Two pre-existing prosediff faults are fixed here because line numbers are what
made them visible: a thematic break reported line 1 for every rule in the
document, and a document whose entire content is "---" panicked in
splitFrontmatter.
c2dd1ef9 — Eugene Blikh 25 days ago
chore(beads): Phase 5b closed, spec-ar4 blocked on phoebe host access

Records the Phase 5b closures (spec-by6.3 and its four children) and the
spec-ar4 finding: the nav restart is still needed — git/meta/todo still show no
spec entry and spec.srht.bigb.es answers 200 — but there is no push-SSH route
to phoebe from this machine, so it needs running on the host.
472bcb1f — Eugene Blikh 25 days ago
feat(web): commentable prose diff with honest anchor state (spec-by6.3.3)

Reverses the diff view's founding rule. renderDocDiff skipped ChangeEqual
outright — "the review shows only what changed" — but any block of a proposed
document must be commentable, so unchanged blocks now render as collapsed,
dimmed context. Changed blocks keep their border, tint and full body, so the
page still reads as a diff at a glance rather than as a document dump. A
context block carrying a comment renders open. ChangeMoveIn now shows its text
too, since a comment control on invisible text is a control on nothing;
ChangeMoveOut stays a bare marker and is deliberately not commentable, because
the same paragraph is anchorable at its move-in position and two anchors for
one paragraph is the bug that avoids.

Every rendered block carries id="b-<16 hex>", hashed from the whole anchor
tuple. Not the page ordinal: an ordinal renumbers on any insertion above it, so
a saved link would silently scroll to a neighbouring paragraph, whereas
including the block hash makes a stale link resolve to nothing instead.

Threads are placed by anchor and by nothing else. Anything no rendered block
claims — an outdated anchor, an old-side anchor whose block the diff no longer
draws, a document the proposal no longer changes — is collected into a
page-level "comments that lost their anchor" area. Never dropped, never moved
onto a neighbour: a comment reads as authoritative about the block it sits
beside, so attaching it to the wrong one is worse than admitting it lost its
place. An edited anchor is drawn on its block and badged.

The comment form's anchor is built at submit time from the branch as it now
reads, through service.AnchorOf — hand-rolling the ordinal conversion here
would put the browser's comments on different blocks than the MCP tool's, which
is the one way two surfaces of one conversation disagree without either looking
broken. The form's block hash guards it: a block that moved while the page sat
open is a 409, not a comment attached to whatever took its place.

That hash is required rather than checked-when-present. Skipping the guard for
a form that omits it would let a later template refactor drop the hidden field
and disable the staleness check silently, with every test still green.

Authority is surfaced, not re-implemented: compose and resolve are the owner's
because service says so and ErrForbidden becomes a 403.

spec-by6.3.3
c6e5d667 — Eugene Blikh 25 days ago
feat(service): review threads and the policy auto-merge gate (spec-by6.3.2)

The comment API above db/, plus the rule that gives comments teeth: an
unresolved review thread suppresses policy auto-merge.

The gate matters because tryAutoMerge runs on every Propose, including an
agent's revision of an open proposal — so a proposal the owner stopped to
comment on could otherwise land unattended on the agent's next push. It gates
policy merges only. MergeHuman does not consult it: clicking approve is the
judgement the thread was asking for, and a comment nobody got round to
resolving must not be able to wedge a proposal shut.

Authority follows from that. The owner opens and resolves threads; an agent may
reply but may do neither, because both would hand the gate's control to the
thing it exists to hold back. An agent reply is not a resolution — answering a
critique is not the owner accepting the answer.

AnchorThreads lives here rather than in each surface, for the reason
Service.Archive does: the review page and the MCP tool must agree about whether
a comment still fits, and two surfaces each segmenting and matching would agree
only until one was changed. That is why service/ now imports prosediff. Each
document is segmented at most once per side however many threads hang off it.

AnchorOf is the other half: a surface offering "comment on this block" has a
document-global block ordinal, and the anchor needs the index within the block's
own heading path. Converting in one place is what stops the web form and the MCP
tool numbering blocks differently and putting their comments on different
blocks of the same document. A test asserts the two agree for every block of a
document.

A thread whose document is no longer among the proposal's changes — the agent
reverted it — is outdated, not dropped: a comment that silently vanished would
look like one that was never made.

Tested against a real Postgres 16, with a control test proving policy
auto-merge still fires without a thread, so the gate test cannot pass for the
wrong reason.

spec-by6.3.2
dd56e38c — Eugene Blikh 25 days ago
refactor(doc): one route from a revision to an Archive (spec-wcr #2, #4)

doc.Scan/DocumentSource were production-dead after service.Archive landed —
their only consumer was doc's own test fixture — and they were the seam that
made web's layering violation writable: a surface could reach past service/
into gitx and build its own archive. Deleted, so doc/ now owns no way to read
a revision and service.Archive is the single route from rev to Archive. The
fixture reads through ListDocuments + FromDocuments instead.

TestScanReportsGitErrors covered an unknown revision failing rather than
walking empty, which gitx did not test itself. Relocated there as
TestWalkOfAnUnknownRevisionFails rather than dropped.

linkHierarchy passed path.Dir(p.Path) where every other call site passes DirOf.
Unobservable today: the two differ only at the space root, and there the
section-proximity step is subsumed by the same-directory step above it, so "."
only ever skipped a lookup that had already answered. Verified by reverting and
re-running. Changed anyway — it stays unobservable only by coincidence of two
ranking rules — with a test pinning the invariant that `parent:` resolves to
whatever the same wikilink in the body resolves to.

spec-wcr
2dc6b717 — Eugene Blikh 25 days ago
chore(beads): enable Dolt auto-push to dolt.srht.bigb.es

Beads changes were only reaching the remote on a manual bd dolt push, so
issue state drifted from the tracker between sessions. bd has native
auto-push (debounced, pushes to the configured origin remote); it was
simply never enabled — sync.remote was set but dolt.auto-push was unset.

Enable it with an explicit 5m debounce rather than relying on the implicit
default. Verified end-to-end: a write fires "dolt auto-push: pushed
successfully", and a fresh dolt clone of the remote shows this session's
closed beads (spec-jjo, spec-mfm, spec-ejq.1) as closed.

Also commits the JSONL exports, which were stale from this session's work.
b87d2866 — Eugene Blikh 26 days ago
bd init: initialize beads issue tracking