web: give bd memories their own view
web: offer the bd command for the issue on screen
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.
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.
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".
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: 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: 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.
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.
fix(web): make the beads view inherit the SourceHut theme
The beads board shipped its own Mardi Gras palette — a purple→green
marquee gradient and saturated solid lane fills — that clashed with
SourceHut's flat, utilitarian dark theme, and the detail pane's field
labels were a hardcoded dark purple with no dark-mode override, leaving
"Created by / Owner / Description" unreadable on the dark background.
Drive everything from CSS variables that mirror core.sr.ht's Bootstrap
palette for both the light default and the prefers-color-scheme: dark
variant sourcehut ships (the same media query it uses), so the view
inherits the host theme instead of fighting it. Lane hues become muted
Mardi Gras accents (gold/green/violet/gray) used only as thin borders and
low-alpha tints — never as body text — so contrast holds on either theme.
The marquee is now a flat stat row, lanes an understated panel with an
accent underline, and detail labels a readable muted caption. Verified
light and dark via a headless render.
feat(web): Mardi Gras beads view
Render a beads (bd) issue database as a Mardi Gras parade board: four
lanes — Rolling (in progress), Lined Up (open & ready), Stalled (open &
blocked), Past Stand (done) — plus a marquee of per-lane counts. Cards
show id/title/type/priority/assignee/labels and blocked-by/blocks counts;
an issue-detail pane (?issue=<id>) shows the full issue with its
dependency edges in both directions and its comments thread.
Fingerprints on the issues + dependencies tables and reads everything
through the browse layer (column-name-keyed, so column order is
irrelevant), capped at 2000 rows/table. Status is bucketed via
custom_statuses with name heuristics as a fallback; "blocked" means
is_blocked or an open blocking dependency. Plugs into the view registry
via init()+RegisterView with no framework edits. Purple/gold/green
palette scoped in the template (no scss rebuild needed).