~bigbes/sr-ht-spec

ref: 2928bf9d79d9ae999a27c74ee5811693489a3a4e sr-ht-spec/web/web_test.go -rw-r--r-- 22.6 KiB
e0938e94 — Eugene Blikh 27 days ago
refactor: make the filter-polarity trap inexpressible

service.SpaceFilter meant "empty membership selects nothing" — a newly
created project has no members — while search.Query.Spaces was a bare
[]core.SpaceRef whose empty case meant every space. Passing a project's
members into a query therefore turned an empty project into the whole
corpus: a silent scope inversion, invisible when it happens, and passing
every test written with a non-empty project.

The filter moves to core.SpaceFilter with unexported fields, and
search.Query takes it whole. There is no slice to hand over any more, so
the inversion cannot be written. Its three states are distinct: every
space (EverythingFilter), exactly these — possibly none — (SpacesFilter),
and the zero value, which is neither. Search refuses the zero value
rather than defaulting it, because both plausible defaults are wrong for
one of the two callers that can produce one, and returns no hits for a
filter that selects no space without asking the index.

service.SpaceFilter is now an alias for the core type, so ResolveProject
and its callers keep their names. Tests that built a Query without a
scope now say core.EverythingFilter(), which is what they always meant.
5a10600a — Eugene Blikh 27 days ago
feat: service.Archive — one accessor, one tree walk, one link graph

web/ and mcpsrv/ both needed a *doc.Archive and each invented its own way
to get one. web/ handed sp.Repo to doc.Scan — reaching past service/ into
gitx, which the layering rule forbids — and then listed the documents a
second time for their bodies, two tree walks per page view. mcpsrv/
converted []service.Document back into []gitx.Document, rebuilding hashes
service/ had already stringified. Two workarounds around one missing
accessor is how three agent-facing surfaces stop being identical.

Service.Archive resolves the revision, walks the tree once, and returns
the archive with the bodies. ArchiveFrom is the same construction over a
Document set a caller already holds — the one place a hex object name is
converted back to a hash, malformed ids refused rather than zeroed.

doc.Page.Links and Page.WordCount were documented as "filled in by a
render pass" and nothing filled them, so Archive.Backlinks always
returned empty; web/ worked around that by re-rendering every document of
the space on each page view. doc.Archive.LinkPass now owns the pass and
the accessor runs it, so the link graph exists wherever the archive does.

web/ and mcpsrv/ no longer import gitx or go-git, in tests either.
doc.DirOf replaces the three copies of the same fromDir helper.
2a7569f4 — Eugene Blikh 27 days ago
feat: web — the Phase 2 read plane UI and its SCSS entry

SourceHut chrome (nav service-switcher, login block, environment banner,
error page, embedded hashed static assets) ported from compare.sr.ht, plus
the read-plane pages: landing, space document tree, rendered document with
frontmatter/backlinks, and search.

The URL grammar is the design's pinned one: a document's address carries no
extension, ".md" is raw source, ".json" is metadata plus body, and ?rev=
pins any of the three to an immutable revision. Links rendered inside a
pinned page keep the pin, so following one does not silently land on the
approved head.

One human and no visibility levels, so the read ACL is one line: the owner
and its agents read, everyone else is redirected to meta's login (a browser)
or refused with 401 (a client asking for .md/.json).