cmd: tell caches that /query answers per credential
gitx, doc: finish retiring the render cache from the comments
The previous commit corrected the five sites the issue named. Four more said the
same thing and were left because they sat outside that change's file set: the
Blobs map in gitx, Page.Blob in doc, and the two test messages that quote them.
A blob sha is a content identity here, and that is all it is. Saying so in every
place that mentions one is the point: the claim survived this long because nine
files corroborated each other, and a repository that donates code copies its
comments along with it.
service: stop documenting a render cache that was never built
Config.Cache was documented as "the bleve index and the blob-sha-keyed render
cache", and four more files corroborated it. There is no render cache: the only
consumer of the value is filepath.Join(cfg.Cache, "index") in the daemon, and
Service.CacheDir() had no callers at all.
The comments now say what is true, and CacheDir is gone rather than
re-documented: Config() already exposes the value, so an accessor with no
callers is only a place for the claim to come back.
Removing the claim beats building the cache, and not only because building it is
the larger job. The pass that costs anything per page view is Archive.LinkPass,
and its per-page output depends on the whole revision — wikilinks resolve
against sibling documents — so its key is the commit sha, not a blob sha. A
blob-keyed cache would have missed the work it was justified by. A rev-keyed
archive cache is worth having and is its own design.
graph: move the hand-written helpers out of the generated file
web: expect the space the replacement rule now keeps
The golden row pinned "[-epsilon-]{+CHANGED+}", which is the fused rendering
prosediff has just stopped producing. The expectation described the defect, not
the behaviour, so it moves with the fix.
prosediff: name the token the replacement rule reads
a[i-del.n] only makes sense once the reader notices that i has already
advanced past the deletion. Say so, and give the token a name.
prosediff: pin the case where the two revisions disagree about the boundary
The replacement rule reads both sides, and dropping either read survived
the test suite. It should not: without the new side's flag a boundary the
proposal ADDS is invisible ("config.yaml" -> "config. yml" renders as
"config.[-yaml-]{+yml+}", so the reviewer reads the new text as one word),
and without the old side's a boundary it REMOVES is. Two cases, one per
clause, and each now dies to its own mutation.
prosediff: keep the space between a deleted word and its replacement
An insertion that directly replaces a deletion is rendered immediately after
it, so its Space flag answers "does a separator belong between the two"
rather than "was there whitespace before this text". It was hardcoded to
false, which fused every one-word substitution: "поднимается" -> "стартует"
reached the reviewer as "поднимаетсястартует". The del/ins styling keeps that
unambiguous, but a replacement is the commonest edit a proposal makes.
The flag is now true when the slot the two runs share sits on a word
boundary — either revision wrote whitespace in front of it, or nothing is
rendered before it at all — and stays false when the slot is glued to what
precedes it, so config.[-yaml-]{+yml+}, foo([-bar-]{+baz+}) and yes[-,-]{+;+}
are not split into two words. At token level a whole-word replacement and a
mid-word one are the same edit, and the token Space flag is the only thing
that tells them apart.
Decided in the package rather than in a renderer: RenderText, the web diff
rows and doc.Inline all read Span.Space with the same three lines, and an
inference repeated in three places is three chances to disagree about one
diff.
scss: name the browser the inline-code measurement was actually made in
The rule was written with the version number the neighbouring comments
carry (141) rather than the one on this machine. A comment is a claim, and
this one is checkable.
web: open a document's diff with what the diff amounts to
prosediff.Stats is computed on every comparison and was used only to answer
'did anything change at all'. It now says so on the page: how many blocks
were added, modified, removed and moved, and how many words came and went.
That is the question a reviewer asks before scrolling — whether this is a
proposal to read in a minute or one to sit down with.
One line at the gutter's size and colour, above the table and inside the
diff's own container so it travels with the document it describes. Kinds
that did not happen are left out rather than stated as zeroes, and the word
counts drop out entirely when nothing was reworded, which is what a
proposal that only moved blocks around reports.
scss: draw a removed paragraph as removed, not as a peer of the new text
A deleted block rendered at full strength competes with the paragraph that
replaced it, and a reviewer skimming the page reads two paragraphs where
the proposal has one. It is now struck through and quieted — on the cell,
so the strike propagates to everything inside it, emphasis and links
included. Measured in Chrome 141: line-through and gray-600 against an
unchanged block's none and gray-900.
Flowed blocks only. A deleted line of a code fence carries the same class
and is left alone: the strike runs through the punctuation that carries the
meaning, and the line already has its bar, its sign and its number.
Inline code needed saying separately in all three quieted places. The
shared partial gives it a hard colour rather than an inherited one, so a
span of code stayed at full contrast inside a dimmed paragraph and read as
the one part not being removed.
web: set a rewritten block's two revisions side by side
Below the similarity threshold the marks are unreadable, which is why the
threshold exists — but the fallback stacked the old paragraph above the new
one, and stacked they read as a removal followed by an unrelated addition.
They are two answers to the same question. So the fallback is now one row
carrying both revisions, drawn as two panes in the order the two number
tracks read, and the row is a modification rather than a delete plus an
insert, because that is what happened to the block. The threshold itself
and its justification are untouched.
That made a spacing defect in sideSpans visible: one side of a word script
is not a paragraph by construction. Measured on a real RFC, «всё [-, что
нужно-] {+нужное+}» rendered as «всёнужное» because both spans record
Space=false, and «внутри {+него+} [.]» rendered as «внутри .» because
carrying the dropped span's separator forward is wrong in the other
direction. Where a span was dropped the separator is now decided afresh.
web: draw a code fence as code, not as flat monospaced lines
A fence, a frontmatter block and a raw HTML block keep their per-line
numbers — the line really is the unit there — but they are now lexed with
chroma and emitted as pygments class names, which is the vocabulary
core.sr.ht's shared scss/highlight.scss already speaks. No HTML formatter
and no inline styles: the deployment's CSP has no style-src 'unsafe-inline'
and the page follows prefers-color-scheme, so the colours cannot be baked
in. The block is lexed whole rather than row by row, because a lexer that
cannot see the line above does not know it is inside a string.
Each line also gets its own scroll container. 'white-space: pre' in a
fixed-layout cell pushes a long line's overflow up to the page: measured on
a real RFC with one 280-character line of JSON, the document scrolled to
2748px against a 1440px viewport, and with the container it is 1440.
web: check a log hit's fragment against the page it links to
The anchor defect survived because the producer and the consumer were tested
apart: doc/ pinned the shape of the anchor, web/ pinned that hitHref appends
whatever it is handed, and nothing asked the log page whether the id was there.
This drives the whole path — the production indexer splits the log, the search
page turns an entry into an href, and the document that href names is fetched
and inspected for the element the fragment points at.
doc: give a log entry the anchor its heading actually renders
LogEntry.Anchor was built as "e-<date>-<n>" and nothing in the tree ever
emitted an element with that id, so a search hit on a log entry linked to a
fragment the log page does not have and the browser stayed at the top. The
capability was intended, and the page already carries what it needs: a log is
served by the ordinary document handler, so every entry header is an <h2> with
an id. The anchor is therefore read off that id rather than invented — through
the renderer's own generator, so one heading has one spelling of its id and the
document keeps a single anchor scheme.
mcpsrv: retire the claim that the session flag is what keeps a grant honest
Five comments said the read grant is per call because [stateless] is set. That
was true when it was written and it is not now, and the constant's own doc was
the worst of them: it listed a measurement -- flip it, watch these two tests go
red -- that the flip no longer reproduces.
Rewritten to say what each layer actually holds. Gate is identity per HTTP
request and the handoff; bindCaller is the caller of the call; the constant is
whether this surface keeps session state, which it has no use for. The
historical measurements stay where they were made, marked as history rather
than as behaviour.
Only comments changed. No assertion in identity_test.go was touched.
mcpsrv: bind the tool's caller to the call, not to the session
Gate resolves the principal per HTTP request and the tool layer reads one off
the handler's context, and only the SDK's stateless option made those two the
same thing. A constant is a convention: flip it and requireRead goes on
compiling while it answers a propose-only token with the whole approved corpus.
Gate now mints an unguessable key for the principal it just resolved, puts it on
the request it forwards and revokes it as that request returns; bindCaller, a
receiving middleware on the server, trades the key back and rebinds the context
the handler is called with. mcp.RequestExtra.Header is the one per-call channel
the SDK offers in both session modes -- getServer is invoked once per session
when stateful, so closing over a principal there would be the same defect again.
Measured, one session whose handshake carried alpha's spec:read token:
stateful, before: spec_read on a spec:propose-only token -> served the document
stateful, after: the same call -> refused, spec:read missing
The two tests written for the flag now pass with stateless=false as well, which
is the point: they were the only two that turned red on that flip. The constant
stays as belt and braces, and its own argument is unchanged.
ci: prove the database suites ran, not just that the DSN was set
The guard already here answers "did the postgres task export a DSN". It does
not answer "did anything run against it", and that is the question the build
was going green over: a suite that skips every case prints ok and exits 0.
SPECSRHT_TEST_REQUIRE_PG asks the second question of the tests themselves, which
are the only ones that can answer it. Measured on this tree, the difference it
now refuses to hide is db 11.9% -> 82.4%, service 37.3% -> 80.6% and
cmd/specsrht-migrate 69.4% -> 81.2% of statements.
Also corrects the postgres task's claim that the migration-agreement check is
among the casualties. TestSchemaMatchesMigration compares schema.sql against
migrations/ as text and passes with no database at all; what skips is the
replay of those migrations against a real one.
tests: fail rather than skip when a run must exercise Postgres
A skip is the right answer on a laptop with no Postgres and the wrong one on
a builder that started one: the build reports success having exercised none of
the persistence layer. SPECSRHT_TEST_REQUIRE_PG is what tells the two apart —
unset it is a skip, set it the same empty DSN is a failure naming the test.
It is the tests and not the manifest that can report this. A shell guard on the
DSN proves the variable was exported; only the helper that would have skipped
can say that the suites ran on it.
A DSN that is set but unreachable already failed, at the Ping each caller does
next, and stays that way.
api: check the layering the prose has been claiming