web: render memory bodies as markdown, and resolve their references
A memory's value is markdown and always was: `bd remember` stores what was
typed, and what is typed is the same prose the memory files carry — bold
leaders, code spans, fenced recipes, and [[slug]] references to related
memories. The view printed it as pre-wrapped paragraphs, which shows the
source rather than the document and leaves every reference a dead end.
The renderer is web/markdown.go, one shared goldmark with three departures
from stock:
- [[slug]] is an inline parser rather than a text rewrite, so a reference
written inside a code span stays text by construction.
- Raw HTML is escaped and shown, not dropped. The safe default omits it,
and these memories are full of <placeholder> spellings CommonMark reads
as tags; omitting one rewrites SRHT_<NAME>_VER to SRHT__VER silently,
which is worse than showing markup.
- An image renders as a link. An <img> at another host is a request that
host makes on behalf of whoever opened the page.
References resolve across databases, because that is where they point: the
mirroring workflow files a memory by its type, so a related memory is as
likely to be in another tracker as in this one. The index is the one the
issue links already use — issue_prefix and the kv.memory.* keys are rows of
the same config table, so one read per database now answers both questions
and one cached projection carries both. A slug two trackers hold resolves to
the first listed, the page's own database, unlike a prefix two trackers claim
which resolves to neither: the same slug in two trackers is normally one
memory that was re-filed and left a copy behind.
A slug nobody wrote and a slug held by a database the caller may not browse
are one rendering — a muted, inert marker. A link, a distinct class or a
different tooltip would each publish the existence of a private database.
Memory.Paragraphs goes with the paragraphs it existed for.
storage: create databases empty so the first push needs no --force
Every automatic creation path wrote an "Initialize data repository"
commit through WriteEmptyRepo, and that commit is history. dolt decides
fast-forward on the client (actions.CanFastForward over the remotesapi),
so the server cannot forgive the collision: pushing a database that has a
root commit of its own — a beads tracker, anything grown locally — was
rejected as a non-fast-forward and could only land with --force. That is
the whole reason the companion-database recipe starts with a forced push.
push-to-create already provisioned an empty store for this exact reason.
Give the other two paths the same default: /internal/repos, whose caller
is git.sr.ht's post-update hook and therefore fires before its user has
ever pushed, now always provisions empty, and the web form does unless
its new "initialize with an empty commit" checkbox is ticked. The
checkbox buys what an empty store cannot offer — a database that can be
cloned before anything is pushed to it, since dolt refuses a store with
no commits as "contains no Dolt data".
Which is also why the overview of a database with no branches now teaches
push rather than clone: the clone box there quoted a command that could
not work. A store that fails to open is deliberately not treated as
empty — an unreadable database must not be advertised as a fresh one.
ci: publish the apk into artifacts.sr.ht as well
The abuild output goes on to the S3 bucket phoebe re-indexes, and now also to
the ~bigbes/main channel of artifacts.sr.ht, which indexes and signs it in the
same request. One PUT per file with the shared working token; a 409 says the
job ran twice and is a warning rather than a failure.
docs: spell out the clipped-read answers
docs: read_rows answers strings or null
docs: spec the MCP surface and the second round of beads views
ci: run the test suites against a real postgres
Not one of this repository's 28 test files had ever run on the builder: the
manifest went from cache_restore straight to abuild, and options="!check"
meant abuild did not run them either. Every apk published so far was built
from code CI compiled and never executed.
A postgres task brings up a database in the VM and exports DOLTSRHT_TEST_PG;
a test task runs make vet and make test. Measured on this tree, db/ has 10
tests and 9 of them gate on that DSN, so without it the whole persistence
layer is skipped and the build is green regardless; no other package needs
it. Hence the guard: an empty DSN fails the task loudly rather than skipping
every database suite. That guard is what makes !check an honest claim about
where the suites ran instead of a licence to ship untested code, and the
APKBUILD now says so.
make vet and make test rather than bare go commands, because the Makefile is
where -tags gms_pure_go and CGO_ENABLED=0 are named — without the tag either
command pulls go-icu-regex in and wants ICU headers the builder lacks.
remoteapi/integration_test.go prefers DOLTSRHT_TEST_PG and only falls back to
docker run when it is empty, so it never reaches for a daemon the builder
does not have. It is moot either way: the file is behind //go:build
integration and go test ./... does not compile it, as with spike.
No gofmt gate: web/beads.go and four test files are gofmt-dirty on master, so
one would be red on arrival.
ci: export the version instead of sed-ing the APKBUILD
The version task rewrote pkgver= in the tracked APKBUILD before abuild ran.
Go decides the vcs.modified half of the build stamp it records in every
binary from git status --porcelain, so that one sed made every binary in
the apk call itself <sha>-dirty for the life of the package. Measured on
go1.26.5. PKGVER now reaches abuild through ~/.buildenv and the APKBUILD
reads pkgver=${PKGVER:-0.0.0}; a local abuild builds an honest 0.0.0.
One git describe now decides it — a tag, else tag_git<n>, else the family's
0.0.<commit count> — and refs/tags/v* joins allow-refs so a tag builds.
The export alone is not enough: an untracked file at go build time sets the
same flag, and abuild works in this checkout. .gitignore takes /src/, /pkg/
and /tmp/, the last being the one abuild points GOTMPDIR at while
MAKEFLAGS=-j runs three go builds that can see each other's work dirs.
Around it, the shape the siblings converged on: cacher installed through
install.sh (checksum-verified, and the raw curl it replaces had no -f, so a
404 body was chmod +x-ed) in two tasks, since its PATH export lands in
~/.buildenv and only the next task sources it; scss through cacher --exec,
whose seed upload is best-effort, instead of an if-block that made an S3
hiccup fatal and a genuine S3 error indistinguishable from a miss;
--optional and the module-cache repair block in cache_restore; cache_save
after publish, without the cacher exists guards an upload already implies;
and publish gated on a readable ~/.apk-ci.env so a secretless submission
reports what it built instead of failing.
go mod download and NOT go mod download all: all resolves dependencies'
test dependencies and appends their hashes to go.sum, which is a tracked
file and therefore the very -dirty stamp this commit removes. On this tree
it added 426 lines, 671 -> 1097; plain download leaves go.sum untouched and
go mod verify still passes.
APKBUILD gains GOFLAGS=-trimpath -modcacherw, without which the binaries
embed builder paths and the module cache is packed read-only into the cache
tarball that the next build then cannot unpack. package() calls the new
install-files, which does not build: abuild runs it in a fresh process that
never called build(), so make install recompiled all three binaries from a
cold cache and shipped a copy nothing had checked. The bare ls of the
stylesheet becomes make check-css, which counts — web/router.go takes the
first glob match, so two stylesheets are as wrong as none. The three binary
targets lose their if [ -d ./cmd/... ] guards, which now only serve to turn
a deleted cmd/ into a green build that packages nothing.
The manifest is a varchar(16384) and over the cap a branch gets no CI at
all, so the reasoning moved to docs/ci.md and the tasks carry pointers.
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.
rename module to sourcecraft.dev/bigbes/sr-ht-dolt; depend on sourcecraft sr-ht-core
docs: add design spec
Architecture plan the v1 implementation followed: verified upstream
facts, schema, access matrix, remotesapi/auth design, browse subsystem,
config + nginx wiring, phase breakdown, and the post-deploy
verification script.