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.
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.
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.
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.
web: render through ecore's pages and its error page
The page list, the per-page parse loop and the view-template loop are
gone: pages discovers every file in templates/, so a page is registered
by existing, and one that defines no content block is refused at startup
rather than served as chrome around a hole. 404.html and 403.html are
gone with them — the shared error page carries the same body, and its
prose is deliberately the same for a database that is not there and one
the viewer may not see.
The renderer that replaced them closes a leak: the old one wrote
"template render error: "+err.Error() into the response body, handing
the viewer template names and field paths. pages answers a fixed
sentence and returns the error for the log.
reltime and abstime come from chrome.Funcs now; ours called every future
instant "just now", where the shared one says "in 3 weeks".
web: serve the static tree through ecore's assets
discoverStyleHref and the bare http.FileServer are sr-ht-ecore's assets
package now: one hashed-name pattern, the cache policy the hash implies
(immutable for a content-addressed name, an hour for the rest), and a
refusal to publish a directory listing of the build. The unhashed
fallback survives, but only when static/main.css is really there — an
href to a file this deployment does not ship is a 404 per page load,
which is what an empty Resolve exists to avoid.
web: draw the chrome from sr-ht-ecore
The brand, the service switcher, the login block, the environment banner
and the database listing were a local port of core.sr.ht's nav — one of
five such ports on this instance, and they had already drifted. They are
now sourcecraft.dev/bigbes/sr-ht-ecore/chrome, the one copy every custom
service draws from.
Deleted: web/chrome.go entire (navEntry, networkOrder, networkExcluded,
buildNetwork, basePage, loginURL, logoutURL), templates/nav.html,
templates/icons/circle.svg (ecore inlines the identical SVG), the
repoList partial, and the local dict/shortHash duplicates.
Added: one chrome.Service built in newApp from our config section with
the hashed stylesheet href set on it, a chrome.Page per request through
app.page, chrome.Attach on every template set, and chrome.Funcs as the
base of the funcmap. Handlers embed chrome.Page in their view structs
instead of copying its fields; the row browser sets ContainerClass to
container-fluid, since its column count is the table's and not ours.
Three behaviour changes come with ecore's policy, all deliberate: the
profile link now prefers hub's ~username page when hub.sr.ht is
configured (it was always meta's /profile), the brand carries a fixed
15rem min-width so the switcher starts at the same x on every service,
and a binary built without a stylesheet renders bare rather than linking
an empty href. The nav test went with the code it tested — ordering,
exclusions and login URLs are ecore's to cover — and what replaced it
asserts only what is ours: that pages are drawn through the chrome at
all, and that the row browser is full-bleed.
The auth path is untouched: a foreign bearer token is still accepted as
a meta.sr.ht PAT.
gitignore the in-repo git worktree directories
Agent worktrees are created under .worktrees/<branch> (and, in the older
repos of this family, .claude/worktrees/<branch>) so that they never
scatter as sibling directories next to the checkout. Neither path was
ignored here, so a worktree showed up as untracked in every git status
taken from the main checkout.
web: mirror the git twin's description onto companion databases
The internal create endpoint accepts a description, but its only caller
— dolt-git-hook — never sends one: git.sr.ht's push context does not
carry it. Companion databases therefore all sat descriptionless on the
dashboard while their git twins had perfectly good descriptions.
Resolve the description server-side instead: a GitDescriber dependency
(internal GraphQL query to git.sr.ht in the owner's name, the same
network-key trust the hook uses to reach us, pointed the other way) is
consulted on every /internal/repos call. A fresh companion is created
with the twin's description; for an existing one the push doubles as the
sync point — a changed, non-empty git description overwrites the stored
one. An empty git description never clobbers one set in dolt's own
settings, and every failure mode (no twin, git.sr.ht down, no resolver
wired) degrades to no mirroring. The lookup is capped at 3s so the
hook's own 5s POST timeout is never exceeded.
Adds testify as a direct dependency for the new tests.
web: restyle dashboard and database lists after git.sr.ht
Replace the bare 'Your databases' heading and compact grid with the
git.sr.ht dashboard layout: a sidebar column with a welcome blurb plus
block buttons (create database, configure dolt credentials), and the
shared event-list cards for the database list. The repoList partial now
renders each database as an event card — name, visibility as small
muted text (non-public only, like git.sr.ht), description below — and
the profile page picks the same partial up automatically.
Adds a 'lower' template func for the visibility label and drops the now
unused .repo-list grid from main.scss (.event-list ships with the core
theme already).
feat(web/beads): hierarchy in the milestone view
Arrange each milestone as a shallow hierarchy instead of a flat list.
- Put the milestone-typed issue(s) on top as the milestone's heads.
- Nest tasks under their epics via parent-child dependency edges,
with a done/total rollup on the epic row; membership stays label-based.
- Keep remaining members as a loose tail; blocks edges are ignored.
- Read the dependencies table optionally, degrading to a flat list.
- Extend fixtures and real-data logging to cover the hierarchy.
feat(web/beads): promote milestones to their own view/tab
Move the milestone rollup off the board and into a dedicated "Milestones" tab
(a registered View, companion to Beads via the same fingerprint). The tab is
richer than the old board panel: per-milestone progress bar, done/open/in-progress
counts, and the issue list under each milestone (open-work first, closed struck
through), each linking to its beads detail. Issues with no milestone label are
summarized in a footnote. A beads DB with no milestone labels still gets the tab,
showing an empty-state hint.
Removes the board's inline milestone panel and the collectMilestones/BeadMilestone
helpers it used; the grouping now lives in the milestones view.
feat(web/beads): ready markers, milestone rollup, transitive dependency tree
Three additions surfacing more of the bd data model:
- Ready: a ⚡ marker on actionable-now cards and a "Ready only" board filter.
"Ready" mirrors bd's ready set exactly — open, unblocked, not template/
ephemeral. As part of this, parent-child edges no longer count as blockers
(a subtask is not blocked by its open epic), which was over-filling Stalled
and under-counting ready; the board now matches bd's is_blocked/ready
accounting (verified: 111 ready on tarantool-etcd, == ready_issues).
- Milestone rollup: a board panel with per-"milestone:<name>" label progress
bars (done/total), each linking to that label's filtered board.
- Transitive dependency tree on the detail page: the full prerequisite chain
("everything this waits on") and the reverse ("everything this unblocks"),
walked from the edge set with depth/'node caps and cycle guard, indented by
depth. Shown only when it reaches past the direct edges, so it never just
repeats the flat Depends-on / Depended-on-by lists.
feat(web/beads): filters for the parade board
Add a filter bar to the beads board: text search (id/title), issue type,
priority, assignee, and label. Filters are query-param based (GET, sticky
across submits) and applied server-side before lane bucketing, so the marquee
counts reflect the filtered set. Dropdown options are collected from the full
issue set (they don't shrink as filters narrow the board); a Clear link shows
when any filter is active. Verified on the 143-issue tarantool-etcd board
(type=bug → 30).
feat(web/beads): close reason in Comments tab; synthesize subtask-add history
- Close reason placement: show it as a block at the end of the Comments tab
(which carries no closed event), while the History tab shows it inline as the
closed event — so it appears in whichever tab you're on, without duplication
within a tab. (Reverts the outright removal.)
- History now includes dependency/subtask additions. beads logs no audit event
for a link, but the dependencies row records created_at/created_by, so
buildDetail synthesizes "added subtask X" / "added dependency on X" timeline
entries (Kind "dep", own accent). On an epic this surfaces when each child was
linked. Guarded on created_at so older schemas without it emit nothing.
- pre.field-body style de-scoped from .bead-detail so the Comments-tab close
reason wraps (overflow-wrap: anywhere) instead of overflowing.
refine(web/beads): concise label events, drop standalone close reason, wrap long lines
- Label audit events collapse to one line ("added label milestone:m3") instead
of a "label added" header plus a redundant "Added label: …" body. The label
name is taken after the first colon so namespaced labels survive.
- Remove the standalone Close reason block below the activity: the reason
already shows as the History tab's `closed` event, so it was a duplicate.
- Add overflow-wrap: anywhere to the timeline body, comment body, and
field-body pre so a very long single-line reason wraps instead of overflowing.
feat(web/beads): epic view with subtask rollup, tabbed Comments/History
Three additions to the beads issue detail:
- Epic mode: when the viewed issue is issue_type=epic, render a Subtasks panel
— its parent-child children (deps pointing at the epic) with a done/total
progress meter, sorted open-work-first with closed sinking to the bottom.
- Activity is now a two-tab strip (pure-CSS, no JS): Comments keeps the plain
thread; History merges comments and the audit-log events table into one
time-sorted timeline with humanized change lines ("changed status to
in_progress", "updated priority to 0", "closed the issue" + reason).
- Close reason moved from the top metadata table to its own block at the very
bottom, after the activity — it reads as a closing note, not a header field.
Verified end-to-end against a real 143-issue beads store (tarantool-etcd) via
the env-guarded TestRealBeadsStore smoke test; unit-covered with a dedicated
epic/history fixture.
feat(web): Overview/Tables/Beads tab bar + full issue fields on beads detail
Give every database page a consistent three-tab bar — Overview, Tables,
then the applicable views (Beads) — in that order. Previously the bar only
carried [views, Tables] and the Overview page marked Tables active, so there
was no way to tell you were on the overview. viewtabs now takes an explicit
Current sentinel ("overview" | "tables" | view slug) and the tree/table row
pages render the bar too (handlers compute applicable views for the ref).
Surface the issue fields the beads detail pane was dropping. The closure
reason (close_reason) recorded by `bd close -r` was never shown; auditing
against bd's own field set also turned up estimated_minutes, external_ref,
spec_id, and started_at. All render conditionally, so issues that don't set
them stay uncluttered.
feat: auto-provision companion Dolt DBs from git.sr.ht pushes
Add a service-to-service path so pushing a git.sr.ht repo creates a matching
Dolt database at ~owner/name, ready before the user's first `dolt push`.
- web: POST /internal/repos, guarded by internal-IP + network-key `Internal`
auth (not the browser cookie/CSRF). Resolves/mirrors the owner via
auth.LookupUser, then CreateRepo + InitStore, rolling back the row if the
store init fails. Idempotent: an existing companion returns 200, a fresh one
201 — safe to call on every push.
- cmd/dolt-git-hook: the git.sr.ht post-update-script. Delegates every hook
stage to the stock /usr/bin/git.sr.ht-update-hook unchanged (argv[0], stdin,
env, exit code preserved; fail-closed if the delegate is missing), then on
post-update POSTs the companion create and prints a one-time clone notice.
Best-effort: never fails a push, degrades to a warning on any misconfig.
Tests cover the endpoint (provision/idempotent/rollback/bad-input) and the hook
(signed request round-trips through the guard's decryption, notice only on 201).
style(web): restyle the beads parade in the todo.sr.ht idiom
The board still read as a rounded-card kanban from another app. Re-skin
every surface in sourcehut's own issue-tracker language (todo.sr.ht):
flat and square — no border-radius, no shadows, no tints — hairline gray
borders, monospace ids, striped-on-hover rows instead of cards, and
squared bordered labels (the sourcehut .label: 1px border, no radius).
The summary becomes a flat hairline strip and each lane a squared column
with a 2px accent cap. The Mardi Gras hues survive only as small square
swatches and the lane cap, never as fills, so the view looks like a
native sourcehut tracker in both the light and dark themes. Verified via
a headless light/dark render. Test asserts the renamed summary class.