fix(db): map repository_path_key to ErrNameTaken
CreateRepo only recognized uq_repo_owner_id_name as a name collision, but
path is derived from (owner, name) by RepoDiskPath, so a duplicate always
violates both indexes -- and repository_path_key, declared inline on the
column, has the lower OID and is the one Postgres reports. ErrNameTaken was
therefore unreachable in practice and every duplicate surfaced as a raw
23505, breaking all three callers that branch on it:
- /internal/repos returned 500 "create database" instead of an idempotent
200, so git.sr.ht's post-update hook printed "companion provisioning
failed (500)" on every push to a repo whose companion already existed
- the web create form returned 500 instead of 409 "You already have a
database with that name."
- a lost remotesapi auto-create race failed with codes.Unavailable
instead of adopting the winner's row
TestCreateRepoDuplicateName missed it by re-creating under a different
path, which only the name index catches; it is now table-driven over both.
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.
fix(browse): resolve out-of-line text instead of rendering <binary>
Dolt stores text/longtext past a small inline threshold out-of-line,
addressed by a content hash — StringAddrEnc (older writers) or
StringAdaptiveEnc (inline-or-address, current writers). renderCell handled
neither: address-encoded strings fell into the binary-placeholder switch and
rendered "<binary>", while adaptive ones fell through to FormatValue and
rendered the raw address hash. Every long description, close reason, comment
body, and audit-event payload was lost on real databases.
Resolve both through the prolly map's NodeStore (which satisfies
val.ValueStore): GetStringAddr + NewTextStorage().Unwrap for the plain address
case, GetStringAdaptiveValue (string | *TextStorage) for the adaptive case.
Genuine binary/opaque encodings (blob, geometry, json-addr, commit-addr) still
degrade to "<binary>". Fixture grows a >4KB longtext row (folded into C4 so the
commit topology is untouched) and TestRowsResolvesLongText guards it.
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).
test(gozstd-purego): expand shim coverage (interop, concurrency, edges)
Add to the pure-Go zstd shim's suite: reverse interop (shim output
decoded by the libzstd `zstd` CLI, plain and with a dictionary); the
dst-append contract for all four Compress/Decompress[Dict] funcs (dolt
passes non-empty buffers); edge cases (empty, one-byte, incompressible
random, 4 MiB random, large text); race-clean concurrency over the shared
plain coders and a shared DDict; and error paths (garbage input errors,
empty input is a documented benign no-op, wrong-dictionary decode errors
rather than returning wrong bytes). All pass under -race.
build: pure-Go (CGO_ENABLED=0) build via a klauspost-backed gozstd shim
dolthub/dolt/go pulls in two hard cgo dependencies — go-icu-regex (SQL
REGEXP) and gozstd (NBS zstd compression) — which forced a C toolchain +
ICU headers on every build. Both are now avoided so the default build is
pure Go and statically linkable:
- ICU: build with `-tags gms_pure_go`, selecting go-mysql-server's stdlib
regexp fallback. Safe because this service never runs the SQL engine
(it serves bare NBS stores and browses read-only), so it never
evaluates SQL REGEXP.
- zstd: `replace github.com/dolthub/gozstd => ./third_party/gozstd-purego`,
a pure-Go drop-in over klauspost/compress/zstd (already in the graph).
It reproduces the nine gozstd symbols dolt references. dolt is
unmodified.
dolt uses gozstd only in its NBS archive subsystem; this binary hits only
the decompress side at runtime (archive dictionary TRAINING is gc/
archive-writer code we never run — the shim implements it over klauspost
but panics on the trainer errors that only that off-path use could
trigger). zstd frames and dictionaries are standard-format, so
libzstd-authored archives decode correctly; the shim's tests prove this
by decoding plain and dictionary-compressed frames produced by the zstd
CLI (libzstd).
The Makefile now defaults to CGO_ENABLED=0 + -tags gms_pure_go (override
with `make CGO_ENABLED=1 GO_TAGS=` for the cgo variant). Verified:
CGO_ENABLED=0 build of ./..., all unit tests, the real-dolt-CLI
integration + spike suites, and the shim's libzstd-interop tests, all
green with no cgo.
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).
feat(web): pluggable alternative-view registry for repositories
A repository can now expose specialized, read-only "views" chosen by the
shape of its tables, while the generic table browser stays available as a
fallback. A View fingerprints the tables (Applies), builds a model from
the browse layer (Build), and renders its own template; views register at
init time via RegisterView and are dispatched by slug at
/~user/db/view/{slug}. The overview shows a tab per applicable view.
The template loader parses each registered view's template with the
shared chrome, so a new view plugs in by adding two files (its .go with
init()+RegisterView and its .html) plus nothing else — no edits to the
registry, router, loader, or handlers. Build receives the request query
values so a view can offer sub-modes (e.g. a detail pane). No SQL engine
is involved; views read through the existing BrowseSession surface.
feat(remoteapi): auto-create databases on first push to own namespace
Push-to-create: an authenticated, non-suspended caller pushing (or
cloning) an unknown repo under their OWN namespace has it transparently
created — a PRIVATE repository row plus a genuinely empty on-disk NBS
store — then proceeds through the normal ACL check as the owner. Any
other case (anonymous, suspended, another user's namespace, invalid
name) still returns NotFound, so a stranger's namespace is never leaked
and nothing is created.
storage.InitEmptyStore creates the store WITHOUT WriteEmptyRepo: an
"Initialize data repository" commit would make the client's first push a
non-fast-forward and be rejected. An empty store (root = empty hash) lets
the initial push land as the repo's first history. The interceptor
auto-create is race-safe (ErrNameTaken re-fetch) and rolls the row back
if the store cannot be created.
Proven end-to-end (integration): a real `dolt push` to a new name
auto-creates PRIVATE + fast-forwards + re-clones; a foreign-namespace
push is denied with no row created. All prior clone/push/ACL scenarios
still pass.
doltsrht: pass full os.Args to server.New so -b binds the web listener
getopt skips argv[0] as the program name, so os.Args[1:] made it swallow the
first real flag (-b) and silently fall back to defaultWebAddr (localhost),
leaving the web UI unreachable from Traefik. Match compare/upstream: pass os.Args.
rename module to sourcecraft.dev/bigbes/sr-ht-dolt; depend on sourcecraft sr-ht-core
build: source core-go from the instance fork
Replace git.sr.ht/~sircmpwn/core-go with git.srht.bigb.es/~bigbes/core-go
at c2c2f38 (upstream fdb3662 + newer upstream commits + the phoebe-lab S3
UnsignedPayload patch) - the code production actually runs. Requires
GOPRIVATE=git.srht.bigb.es to fetch. Full suite re-verified including the
remotesapi integration scenarios.