~bigbes/sr-ht-dolt

ref: 11c622f38dc9e15a1c23880fca39e2f6b1d8b1b1 sr-ht-dolt/web d---------
11c622f3 — Eugene Blikh 5 days ago
web: stop printing browse errors to the reader

The overview rendered the browse layer's own error text into the page, under
"Could not read history: " — dolt internals and the store's path on our disk,
which nothing else on this surface discloses and which a reader can do nothing
with.

The page now carries a fixed sentence and the reason goes to the log with the
database id (slog + scribe.Err). The view field is a bool rather than a message,
so no error string can reach the template by being assigned to it later. The
empty-database state the page already reported ("No commits.") is untouched:
that is a fact about the database, not a failure.
bee20506 — Eugene Blikh 5 days ago
web: tell a database outage from a missing database

loadRepoForBrowse turned any error from GetRepoByOwnerAndName into a 404, so a
metadata store that could not answer told every reader on the instance that
their database does not exist — for as long as Postgres was down, in the voice
reserved for "there is nothing here". loadRepoForAdmin did the same on the
settings page.

Both classify through repoLookupFailed now: db.ErrNotFound keeps the 404, and
it stays the same 404 the visibility rule renders for a PRIVATE database the
caller may not see, so a masked database and a missing one remain
indistinguishable. Anything else is a 500 through the shared error page, with
the cause logged (slog + scribe.Err) and never rendered.
dc918296 — Eugene Blikh 5 days ago
web: link issue ids to the tracker that owns them
b6a01558 — Eugene Blikh 5 days ago
web: drop the author from a memory's revision line
043b0fd7 — Eugene Blikh 5 days ago
web: answer what is ready across every tracker
0190aab3 — Eugene Blikh 5 days ago
web: register the views in one explicit list
231b7764 — Eugene Blikh 5 days ago
web: give bd memories their own view
3b06523c — Eugene Blikh 5 days ago
web: offer the bd command for the issue on screen
64592988 — Eugene Blikh 5 days ago
web: show how fresh a beads view is

handleView now reads the head commit of the rendered ref onto the envelope
(Head *browse.CommitInfo), and the beads and milestones headers carry a
shared beadsHead partial: <branch> · last commit <relative> · <short hash>,
the hash linking to the commit page and the exact stamp in the title.

The read is decoration on top of an answer: a database with no commits, or
a Log that fails, renders the page without the line rather than 500ing.

The relative time is a new ago func rather than chrome's reltime — it is
past-facing (clock skew reads as "just now", never "in 3 minutes") and
reads a package clock a test can pin.
fe616c89 — Eugene Blikh 5 days ago
beads: add a one-column stream layout beside the board

?layout=stream renders the same filtered set, the same buckets and the same
cards as one column of sections instead of four lanes side by side. It is a
layout of the Beads view and not a fifth tab: filters, the ready toggle, the
search box and ?issue= behave exactly as they do on the board.

A column read top to bottom can afford one sort per section, because each
section answers a different question: Rolling by started_at desc (what was
picked up last is what is being worked on), Lined Up ready-first then priority
then oldest, Stalled by blocker count (one blocker away is nearer than five),
Past Stand by closed_at desc. started_at and closed_at join the card model as
sort keys and are not displayed; a row missing one sorts last, because an unset
timestamp is not a very old one.

Past Stand opens collapsed in a <details> — the largest and least actionable
section, closed without a line of JavaScript. The sections are derived from the
finished lanes rather than bucketed again, so the section counts cannot drift
from the marquee, and the lanes keep the board order the board renders.

The Board/Stream toggle rebuilds the current query with layout replaced, via a
new withQuery template func, so every active filter survives the switch.
7d799ed0 — Eugene Blikh 5 days ago
beads: drop the Bead/Beads prefix from the moved types
f9c82b0b — Eugene Blikh 5 days ago
beads: extract the projection out of web/

web/beads.go held the one reading of the beads schema — the table
fingerprint, the lane bucketing, the ready rule, the status categories,
the transitive dependency walk, the event humanizer, the filter model and
the milestone rollup — where a second consumer could not reach it. The
MCP surface and the cross-database ready page both need it.

Move it to a new root package beads/ that depends on browse/ and the
standard library only: rows in, view model out, no net/http, no
html/template, no core. The BrowseSession seam is declared consumer-side
there and names the one method the projections call, so web's larger
BrowseSession satisfies it structurally and a session passes straight
through.

web/beads.go and web/milestones.go keep only their View adapters — slug,
label, template, Applies, and the hand-off of ref and query. The
templates are unchanged: the moved types keep their names and their
display methods, so every dot still resolves.

Pure move plus the beadsMax -> beads.Max export rename. The projection
tests move with the code and become testify; the render tests stay in
web/ unchanged. Same 63 tests pass before and after.
ed5a65bc — Eugene Blikh 9 days ago
chrome: link the favicon through the shared head partial

The icon href was a literal in the layout, so a build with no static tree — a
test, a binary run out of a working copy — asked for /static/logo.svg once per
page and got a 404 each time. It is the chrome's FaviconHref now: our own logo
when this build ships one, checked the way the stylesheet already was, and
NewService's built-in data: URI when it does not. Both <link>s come from
srht-head-links, so the guard against <link href=""> is written once.

Also pins what the repo-list partial's optional fields do for a service that has
no timestamp in its schema: a card with no muted footer, not "0001-01-01".
9660c720 — Eugene Blikh 9 days ago
pages: read forms through FormValues

Every mutation on this surface read its fields with r.PostFormValue after a bare
r.ParseForm. PostFormValue was already the right half — r.Form would merge the
query string into the body, which would let a mutation be driven from a URL
somebody was linked to, and that request is exactly the one the same-origin
guard sees nothing wrong with — but the body was bounded only by net/http's
10 MiB default, on pages anyone can reach. FormValues is both properties in one
call, and the values are passed down explicitly instead of each sub-handler
reaching back into the request.
0b1e119a — Eugene Blikh 9 days ago
chimw: take the chi helpers from ecore

Three things this service did not have. Read routes are registered for HEAD as
well as GET, so `curl -I` and every uptime probe stop being answered with a 405
and a kilobyte of error page; the twin shares the handler, so it cannot say 200
where the GET says 404. chi's two routing failures now render our own page
instead of net/http's plain text — an unrouted URL here was the one refusal on
the instance that did not look like the service it came from. And the request
line is a slog record rather than chi's colourised line on stdout, which was the
only line this daemon emitted that was neither structured nor on stderr.

chi's own middleware package loses the chimw alias to the package written
against; it is chimiddleware now, as ecore's package doc asks.
d84a877c — Eugene Blikh 9 days ago
internalauth: take both ends of the internal protocol from ecore

The guard on /internal/repos and the header cmd/dolt-git-hook minted for it
were two hand-written halves of one protocol in two packages that shared no
type, no constant and no test. Both are now sr-ht-ecore/internalauth: Guard on
the receiving end, AuthorizationAs on the calling one, over one Auth struct.

The guard also pins the caller, which the old copy did not: core-go only asks
that a token name some client and node, and on an endpoint that provisions a
database for an arbitrary user that means any holder of the network key will
do. The pinned pair lives in core so the mint and the pin cannot drift apart.

The hook test now runs internalauth.Identify — the real receiving end — over
the header the hook produced, so the two ends are checked against each other
rather than against a third copy of the decode.
6af58599 — Eugene Blikh 9 days ago
web: take the login redirect from chrome.LoginURLFor

Building a whole Page resolved the nav, the brand and the profile link
for a response that is a Location header and nothing else.
27823bb6 — Eugene Blikh 9 days ago
log: replace logrus with slog behind auxilia's scribe handler

Every logger field this service owned was a *logrus.Entry threaded
through a constructor, which is what logrus costs for want of a usable
default. They are slog.Default().With("component", ...) now, and the
threading is gone with them; the shared middleware's panic reports land
in the same handler, which is why the daemon sets the default before
anything that can fail.

The handler is scribe's tint handler: level from [dolt.sr.ht]log-level,
source positions, and masks keyed on the attribute path for the three
credentials this service handles — the unified-login cookie, the
Internal fernet token and the Authorization header the remotesapi reads
a PAT or a keypair JWT out of. Errors go through scribe.Err, so a culpa
error's hint reaches the operator on its own line.

logrus stays in go.mod: dolt's remotesrv.ServerArgs takes a
*logrus.Entry and nothing else. It is now confined to Config.DoltLogger,
which is the only place this service names it.

dolt-git-hook is deliberately untouched: what it writes to stderr is the
notice a pushing user reads through git, not a log.
84c33df2 — Eugene Blikh 9 days ago
test: build the fixture config and the keyset with ecoretest

The hand-built ini in web_test.go, the random fernet key in authn's
TestMain and the same seeding copied into the git-hook test are one call
to ecoretest now. The keys are fixed rather than generated on purpose:
they secure nothing inside a test process, and a constant keyset is what
lets two packages of this service initialise without the second rotating
what the first sealed with.

The synthetic instance runs in production mode, so the environment
banner is off in tests unless one asks for it.
0af9ccda — Eugene Blikh 9 days ago
web: guard mutations with ecore's csrf, cache and panic middleware

checkSameOrigin and originMatches are gone, and with them the three
per-handler calls that had to be remembered: csrf.Require sits over the
whole browser group, so the mutating route added next year is guarded by
being routed. The internal provisioning endpoint stays outside that
group deliberately — it is a service-to-service POST with no Origin and
its own network-key guard.

middleware.PrivateCache marks every page as one no cache may reuse for
the next viewer, which is only correct because the static handler opts
out per asset once it has found the file. RecoverPanics answers a panic
with the error page, and one that arrives after the response has started
by dropping the connection rather than appending an error to half a
document.
Next