50 ancestors of d8a35694f09b67ad
|
2026-08-24
| ||
| 23:05 | Suite green; merges clean. Already deployed — the running binary carried this while the site was down, and trunk now matches it. check-in: d8a35694f0 user: dmestas tags: trunk | |
| 22:59 |
Bind the port without waiting on every repository
The forge could not answer for minutes after starting. Register runs before ListenAndServe and probes each store with 'zit submanifest show', and the note there said it costs ~250ms — measured on small stores. On tcl it is 116 SECONDS, on sqlite 16. A fleet with either in it held the whole site down. The probe is bounded at 2s now. Past the deadline it is simply an unanswered question, which the existing false already handled: the fossil clone URL is withheld rather than offered wrongly. Registration is also timed and logged, because each repository's cost is the whole forge's downtime and a slow one should be nameable rather than suspected. closed check-in: 65e953863a user: dmestas tags: startup-latency | |
| 22:17 | merge search-current into trunk check-in: 37c9599a15 user: dmestas tags: trunk | |
| 22:17 |
search: match the current revision, not any revision
A wiki page and a technote are EDITED: every revision is its own artifact under one stable name. Keyed by artifact hash they piled up in search_doc, and Search's group-by then returned whichever revision matched BEST — so a query found text deleted years ago and linked to a page that no longer contains it. On the live fleet "terrified" matched spatialite_gui's wiki home, which has not said it since 2016; libfossil/home had 117 revisions all searchable. One row per page now, holding the current revision, newest wins on date so a clone may deliver revisions in any order. Every revision stays readable in the page's own history, which is where the question 'what did this used to say' belongs. Existing indexes are rebuilt from wiki_edit and technote at open rather than left to the fold: the fold only fires on artifacts that arrive after this code, so deleting the stale rows alone would empty wiki search until somebody happened to reindex. closed check-in: e262fc246c user: dmestas tags: search-current | |
| 21:40 | merge hit-meta into trunk check-in: c4b5ef5d95 user: dmestas tags: trunk | |
| 21:40 |
search: results say what tells them apart
"use after free" on tk returned three rows identical on screen — same repository, same title, same snippet — because the same fix landed on three branches and the row carried nothing that differed. The only way to find out which one you wanted was to open all three. Rows now carry the hash, date, author and branch for a check-in; the status and date for a ticket; the date and author for wiki, forum and technote. A doc or a file is identified by its path and gets nothing, rather than a date nobody asked for. Decorated by primary key, for the page actually shown: the FTS query groups over a match set that can be thousands of rows, so joining there would decorate all of them to render fifty. On the fleet, decorated AFTER the merge trims — every repository is asked for the full limit so the merge can pick freely, and decorating before it meant 25 indexes times 50 rows to render 50. One definition of the line, shared by both search pages. They are separate templates, which is why the first version of this reached only the per-repo one. closed check-in: 5cc9306b59 user: dmestas tags: hit-meta | |
| 21:19 | merge tree-search into trunk check-in: cd06e1345b user: dmestas tags: trunk | |
| 21:19 |
search: the tree is searchable — documentation and source
quarry rendered both as first-class surfaces (the docs tab, the file browser) and could find neither. Fossil searches documentation but not source; this does both, from one pass. One tarball, not a read per file. cat per path costs a subprocess and a store open each: 2,199 files on sqlite is ~31s, against 2.2s for one archive — the tarball is the whole tree in one pass, which is the shape of the question. Tip-only. Indexing every revision of every file would dwarf the rest of the index many times over to answer a question nobody asks; what a file used to say is a question its own history answers. ref carries the version, not just the path: a link has to name the check-in the row was indexed at, and resolving a branch name at click time would be a second source of truth about which tree the row describes. Binaries and files over 1MB are skipped and counted — a search that silently covers less than the tree beside it is the failure this change is about. closed check-in: 8df971b482 user: dmestas tags: tree-search | |
| 20:29 | merge doc-reindex into trunk check-in: 1631ff8ecc user: dmestas tags: trunk | |
| 20:29 |
doc index: survive a rebuild, and never wipe on a failed read
Reset drops every derived table, doc rows included, and the doc pass only fired on a tip change or once at boot — so a reindex left documentation silently unsearchable until somebody happened to commit, which on a mirror of a quiet project is never. Observed live: fossil's 173 documents went to zero after a rebuild and stayed there. ReplaceDocs swaps the whole set, so a pass that found documents and could not read them would overwrite a good index with nothing. It now keeps the previous set and says so. A tree with genuinely no documents still writes an empty set, which is true. closed check-in: 24586a4771 user: dmestas tags: doc-reindex | |
| 20:21 | merge doc-search into trunk check-in: 547934f37f user: dmestas tags: trunk | |
| 20:20 |
search: documentation is searchable (fossil's scope "d")
quarry rendered tree documents as a first-class surface — the docs tab, the reading furniture — and could not find them. A reader could open www/quickstart.wiki and get nothing searching for it. Fossil searches c d t w e f; quarry had everything but d. Indexed at the default branch tip only, asynchronously, on the trigger warmReads already uses: a round that moved the tip, plus once at boot. Not through dagnats — dagnats is optional, a forge without one "simply has no CI rather than a broken one", and search must not inherit that. This is local derivation from the store, the same class of work as the fold, which Reindex already routes around the bus on purpose. isDocPath is exported so the indexer and the docs listing share one definition of what a document is; two would drift and the drift would be invisible. Adding the kind exposed how many places the list is written down: the index, two ops validators, the scope chips, hitOf. The validator was missed and refused every doc search before it reached the query. Two tests now hold the four in agreement. closed check-in: 57a2f8e3d2 user: dmestas tags: doc-search | |
| 20:17 | Suite green; merges clean after clone-counts landed. check-in: d4add44f21 user: dmestas tags: trunk | |
| 20:16 | Suite green; merges clean. Proven against a real server: two clones read 2, a pull leaves it at 2. check-in: bc4fbd1c79 user: dmestas tags: trunk | |
| 19:59 | merge search-links into trunk check-in: 11c9c4a43b user: dmestas tags: trunk | |
| 19:59 |
search: forum and technote results had no link at all
They fell through hitOf's switch with no URL, so the title rendered as
— which a browser resolves to the current page. Clicking a
forum result reloaded the search. The index found them, the facet chips
offered them, and the one step that turns a hit into somewhere to go
was missing.
Their refs are not artifact hashes: searchDoc files a forum post under
its thread root and a technote under its event id, which is what a
reader wants to land on.
The test walks index.SearchKinds rather than a hand-written list, so a
kind added to the index without a link here fails instead of shipping a
dead result. | |
| 19:47 |
Settings fits a phone
The capabilities table sized to an <input size=28> it could not shrink, so the whole page was wider than the viewport and every field ran off the right edge. It scrolls in its own box now. Webhooks were a three-column table with an input and three buttons in one cell — the same bug, added by me. Rows stack instead. Help text cut to one line; the noun chips and the field's datalist say the rest. closed check-in: 0a60aac30c user: dmestas tags: settings-mobile | |
| 19:16 | RFC 0005: state what the clone counters count closed check-in: c83148c71d user: dmestas tags: clone-counts | |
| 19:16 |
Count clones and fetches
A clone reads and writes nothing, so it is the one kind of forge activity that leaves no trace in the DAG at all. The sync edge classifies the protocol's cards: clone SEQNO 1 opens a new clone, later rounds do not, so a big store cloned in forty rounds still counts once. Two sinks: the vocabulary event for anything reacting, and a JetStream counter for how many, which converges across nodes instead of losing increments to a read-modify-write. check-in: dc058fa551 user: dmestas tags: clone-counts | |
| 18:08 |
Suite green across 29 packages; merges clean.
Four commits: the fan-out onto the vocabulary, per-subscription event selection, the settings surface, and the RFC correction that preceded them. check-in: 69fc3f06d1 user: dmestas tags: trunk | |
| 17:20 |
Webhook settings on the repository's own admin screen
Subscriptions were CLI-only. The settings page now lists them, takes an endpoint plus its event patterns, pauses, resumes and deletes — gated on 'a' like everything else there. Patterns are checked before saving. The section is gated on the store being attached, not on the list being non-empty, or the first subscription could never be created. closed check-in: 1c5d758256 user: dmestas tags: hooks-vocabulary | |
| 17:16 |
Subscriptions select their events
A webhook took everything or nothing. It now names vocabulary patterns: '*', a bare noun for every action on it, or an exact kind. Checked at the moment it is set, because a webhook that silently delivers nothing reads as a broken forge. check-in: e9547dcc49 user: dmestas tags: hooks-vocabulary | |
| 17:13 |
Webhooks fan out from the vocabulary, not from the ops layer
A ticket closed by push notified nobody, because only ops queued webhooks. One consumer on quarry.evt.> turns every raised event into deliveries, so arrival path stops mattering. Deletes kindOf: the delivery's kind was sliced off an idempotency key. check-in: f2ea5f5c1a user: dmestas tags: hooks-vocabulary | |
| 17:03 | @dmestas I love the strata idea check-in: edcc983217 user: dmestas tags: trunk | |
| 16:57 |
Close the push gap: derived events all come from the fold
The index holds the state an op would have read, so a pushed artifact gets the same event a forge op would have raised. Ticket transitions and landing verdicts compare folded state before and after; forum, wiki and technote need only the artifact or one lookup. Op sites stop raising derived events, so 2.1's split is now enforced rather than documented. check-in: 06942dcc5d user: dmestas tags: trunk | |
| 16:37 |
RFC 0006: strata — stacked landings, rendered as a chain
The forge half of zit#144. A stack is a chain of dependent landings that land bottom-up; quarry renders the chain, refuses accept above the bottom, and flags a segment whose base was rejected. No new index and no new artifact: the edge is already implied by a landing's target, so a stack is a reading of landings the way a landing is a reading of forum posts. closed check-in: 041ca46258 user: dmestas tags: strata-rfc | |
| 16:32 | RFC 0005: record what implementing the derived events taught check-in: 17317e830f user: dmestas tags: trunk | |
| 16:32 |
Raise the derived events from the fold
checkin.created, branch.created and tag.* come off the artifact; branch.moved comes from the computed branch after RecomputeBranches, because only a branch's first check-in carries a sym- tag. Raising is not idempotent while folding is, so the fold asks the index which hashes are new before writing them. A rebuild raises nothing. check-in: a1b816cd96 user: dmestas tags: trunk | |
| 16:27 | Add the new event-plane files the previous commit omitted check-in: f485551646 user: dmestas tags: trunk | |
| 16:27 |
Publish the vocabulary on quarry.evt.<repo>.<noun>.<action>
RFC 0005 picked zit.evt., which the fold consumer already claims with a trailing wildcard — every vocabulary event would have been handed to the artifact parser. Own stream, own root, tests pinning both. check-in: 3bca39d8f5 user: dmestas tags: trunk | |
| 15:57 |
RFC 0005: dagnats already has the triggers; correct the record
I probed HTTP and concluded dagnats had no scheduler. Wrong lens — triggers are NATS and KV native. It has cron, subject, webhook, durable HTTP, worker-contributed kinds and debounce. So quarry builds none of that: it owes well-named subjects, and the vocabulary IS the hierarchy. check-in: 3aef7f3211 user: dmestas tags: trunk | |
| 15:50 |
events: the vocabulary as code, and what the bus really does
RFC 0005 §1 as internal/evt, named at the eight sites that raise events instead of sliced out of a TxnID — so a ticket change that closes one is ticket.closed, not ticket. Measured the window: a gap IS detectable (first_seq - ack_floor) but the next fetch succeeds silently, so every consumer must check. dagnats has no scheduler; quarry owns the clock. check-in: f08add75f5 user: dmestas tags: trunk | |
| 15:36 |
RFC 0005: verified the bus, added schedules and dispatch
The stream is a one-hour window, so events split: DAG-derived ones are re-derivable after a gap, operational ones need OLTP backing. A cron run and an ad-hoc run are not events — three trigger sources, and schedules take fossil's 'a' so a repo admin owns their own. check-in: 60206e85b2 user: dmestas tags: trunk | |
| 13:11 |
RFC 0005: one named event stream, three consumers
Events are noun.action, subscribed by name. Fossil has no such vocabulary — its hooks are artifact-level and its alerts are eight letters — so the delivery semantics come from its warning that a distributed system cannot promise exactly-once. check-in: dc2bb819a3 user: dmestas tags: trunk | |
| 12:28 | merge timing-wall into trunk check-in: 952100a76b user: dmestas tags: trunk | |
| 12:27 |
timing: the footer reports wall time, not a sum of concurrent calls
A diff is two zit subprocesses on purpose, run at once. Adding their durations printed more time than the whole request took — "rendered in 410.1ms (zit 706.8ms×2)", and "79.94s (zit 159.05s×2)" on a cold tcl page. A number larger than the total reads as a broken page rather than as two calls overlapping. Record now takes the start instant instead of a duration, because a duration cannot say whether two calls overlapped, and the union of the spans is what a reader actually waited. closed check-in: 4aaf089616 user: dmestas tags: timing-wall | |
| 10:47 | merge people-admin into trunk check-in: f8af01dec3 user: dmestas tags: trunk | |
| 10:47 |
people: an admin sees which contributors are also accounts
A contributor is a name an author's client wrote on an artifact; an account is a login on this forge. On a mirror the two barely intersect — fossil has 151 contributors and this forge has two accounts, sharing none — so the page says so, and badges the few that are both. Read-only on purpose. A grant control here would offer to give write access to a string, and on a mirror those strings are chosen by whoever can commit upstream. The door goes to settings, where every row is a real account. account.list is forge-wide and site-admin only, so a repo admin gets the door without the badges rather than an error. closed check-in: 8fb0a669aa user: dmestas tags: people-admin | |
| 10:25 | merge repo-people into trunk check-in: 5757eeaaeb user: dmestas tags: trunk | |
| 10:25 |
people: a repository's contributors, inside the repository
"151 contributors" in fossil's About panel went to the fleet page: 481 names across 25 repositories, repo nav gone, search rescoped to the whole forge. people.list has taken a repo parameter all along and nothing in the UI reached it. The per-person line differs from the fleet's on purpose. Counting repositories inside one repository always says 1, so the span of somebody's work here is the fact worth carrying instead. Contributors(0) meant a hundred, silently, which is why the listing said "of 100" under an About panel saying 151. It means all of them now. closed check-in: 278e021086 user: dmestas tags: repo-people | |
| 09:52 | merge landing mimetype with trunk check-in: cfd042dd99 user: dmestas tags: trunk | |
| 09:52 |
landings: say the mimetype, and keep headings to one line
zit#136 shipped --mimetype, so a remark written in the composer is stored as the Markdown it previewed as. zit#138: the title card holds the whole -m message, so the heading takes its first line. check-in: 73367a3820 user: dmestas tags: trunk | |
| 09:51 |
about: the notes and announcement rows join the panel's icon grammar
Every other row in the About panel is icon + label, and .about-sub indents 1.5rem to clear that icon. These two had no icon, so their excerpt and date hung indented under nothing. A wrapping title also needs baseline alignment, or its later lines slide back under the icon. The note now says who wrote it, as the feed does. check-in: d6a6146502 user: dmestas tags: trunk | |
| 09:46 | merge notes-first into trunk check-in: 116d10e5cb user: dmestas tags: trunk | |
| 09:46 |
about: a project's news comes before its version list
Notes sat third in the sidebar, under Tags. What a project has been doing is a better first answer to "what is this" than which versions it has cut — and with no tab, this is a main door to them, which reads poorly third. closed check-in: d1dde173e0 user: dmestas tags: notes-first | |
| 09:41 |
landing page: probe the tip pair instead of scanning for the record
zit#134: landing show has to walk the whole store to FIND the record before it can answer about it, and quarry already folded that record out of its own index. zit merge --probe names the two check-ins, so the walk disappears -- 0.005s against 0.18s on this forge's own store, and the gap widens with store size, not with the number of landings. Probing the same tips the cache is keyed on also closes a race: the key was built from resolved tips while the probe re-resolved a branch name, so a push landing between the two cached an answer under a key it did not describe. An unresolvable tip now skips the probe instead of keying on ':' -- every landing whose branch could not be resolved shared that one cache entry and could be served another landing's verdict. check-in: bb05986ad8 user: dmestas tags: trunk | |
| 02:57 |
notes: the notes-filtered timeline is a door, not a dead end
Without a tab, /timeline?y=e is where somebody browsing notes stands, and it offered no way to the page that reads them as a feed. The other two doors — the About panel and the docs page — are not where a reader who just filtered a timeline is looking. check-in: 16e8738cb6 user: dmestas tags: trunk | |
| 02:40 | merge forum/landing separation with trunk check-in: f0f4f7c525 user: dmestas tags: trunk | |
| 02:39 |
forum: landings are not forum content
A landing is a forum thread — that is what a stock fossil clone reads — but it is a decision record with its own surface. Off the listing and out of the count, which must agree. An existing thread URL redirects to the record rather than 404ing a link that used to work. check-in: 302ca7fae6 user: dmestas tags: trunk | |
| 02:39 | merge notes into trunk check-in: 1ff2886793 user: dmestas tags: trunk | |
| 02:38 |
notes: one row per technote, not one per edit
Fossil registers one timeline event per technote; quarry registered one per artifact. libfossil has 50 technote artifacts under 26 event ids, so its timeline showed a superseded title five rows above its own correction. The collapse is in the event trigger — insert, then evict every loser for the event id — which is order-independent, as the fold requires: a clone delivers artifacts in wire order. Notes are no longer a tab. Fossil's built-in menu has none, and libfossil, which uses them as a milestone feed, overrode mainmenu to add TODOs and FIXMEs without adding one. They are reached from the About panel, the docs page and the timeline filter instead, and the listing is a feed with excerpts rather than a list of links. Every edit stays readable, as a wiki page's revisions are. Fixes an authoring bug found on the way: zitcli never passed --time, so an edit re-filed the note at the moment it was edited and it moved in the timeline. The filing time is now a field on both forms. closed check-in: ac80214995 user: dmestas tags: notes | |