Skip to content

Feat/ledger docs site - #226

Open
barisakcam wants to merge 18 commits into
COVESA:mainfrom
barisakcam:feat/ledger-docs-site
Open

barisakcam wants to merge 18 commits into
COVESA:mainfrom
barisakcam:feat/ledger-docs-site

Conversation

@barisakcam

Copy link
Copy Markdown

Host the ledger explorer on the docs website

Moves the ledger from playground/ into a shared
src/s2dm/templates/ledger-ui workspace beside insights-ui, so the
scaffolded Docusaurus site renders it too. The playground keeps only its own
shell — file list, help button, collapsible details pane.

Two seams were needed: configureSqlJs takes the wasm URL from the host,
since sql.js can't resolve it under two bundlers, and openLedger carries
{ name, bytes } instead of a File so a host that ships a ledger dispatches
the same action as one that opens a picker.

The ledger reaches the site the way the schema does — staged in
dist/ledger.db, turned into static/ledger.db by npm run doc, supplied by
the workflow's new ledger_source input or preview-docs.sh. A model without
one still builds. On the site it gets the docs sidebar, a route per view:
Schema, Raw Tables, Explore, Query.

Most of the styling work traces to one fact — the site loads Tailwind's theme
and utilities but not its preflight, and Infima's rules are unlayered, so
utility classes can't outrank them. Each affected default is restated in
custom.css at a layer where it wins.

Reads a user-supplied SQLite ledger entirely in the browser via sql.js,
with no backend endpoints involved.

- Workspace selector separates Schema (Explorer/Insights) from Ledger
- Left sidebar loads a ledger and summarises active/total counts per table
- Raw Tables: table dropdown, search, kind and status filters, pagination
- Explore: one search across every table, results grouped per table
- Query: predefined queries plus editable read-only SQL, enforced by
  PRAGMA query_only
- Details sidebar shows a record's Concept -> Revision -> Contract ->
  Binding context with expandable nodes and record-to-record navigation
- Table order, level links and record identity are derived from the
  database's declared foreign keys
Add case-sensitive, whole-word and regex search across Raw Tables and
Explore, backed by a registered SQLite regex function, plus kind and
status filter dropdowns.

Give the details pane record actions that navigate to any record a row
references, and to that record's page in Raw Tables. Add a mermaid ER
diagram of the ledger shape behind a button in the sidebar.

Replace the query presets with simpler ones that exercise these paths.

Fix chain resolution to link levels by foreign key rather than by table
order or table name, so a sibling table or an irregular plural no longer
hides a subtree, and bound the walk so a wide ledger cannot freeze the
tab. Keep a loaded ledger when an import fails, report the reason, and
close a superseded import instead of leaking it.
Show each table's columns and the relationship diagram in all three
ledger views, not only in Query. Drop the collapsible wrapper, the
empty-state text and the separator from the ledger file list, which
holds one file at most.

In the query toolbar, remove the read-only hint and move the selected
preset's description below the selector, keeping its line reserved so
the toolbar does not change height.

Hide the Explorer and Insights tabs until a schema is loaded, replacing
the prompt that was duplicated into both tab bodies.

Fix the theme flashing to the system setting when switching workspaces:
ThemeToggle is remounted each time and read its stored value one render
too late.
Derive the ModL model from one declaration and fall back to it when the
uploaded database declares no keys, so the chain, the table order and the
page ordering survive a file exported without constraints. Take the shape
from modl/ledger.py, and make the table icons and status tones exhaustive
over it.

Fix the correctness findings: a second ledger no longer inherits the
first one's details, matches and query result; a failed import leaves the
loaded ledger alone and says why; a superseded import closes its own
database; chain levels link by the foreign key that declares them rather
than by table order or name; search folds case for non-ASCII text in
every mode; a repeated column name no longer highlights the wrong row.

Split the four largest files along the seams their signatures already
drew: introspect into sql, schema, rows, query and search; the slice and
the saga into one per concern; chain into a pure derivation and a
database walk. Extract the shared details-pane shell, the search input,
the record list and actions, the table footer and filter selects, and the
two help panels. Dissolve constants.ts into the modules that read them.

Reduce the repeated work each read did: two queries per page instead of
three, one count per table instead of two, and one pass over the chain
instead of one per node.

Style the record details as bordered rows with tinted column names, to
match the insights detail views.
Match the ledger file row to the Schema file list: the same container
padding and list spacing, and a placeholder in the leading slot so the
name and the row height line up with a list that has a drag handle and a
per-row remove button.

Give the table list and the relationships button the same width as the
file row, which the container's wider horizontal padding had held back.

Drop the status badge from the details title, and move the tint in Record
details from the column name to the value, which is what a reader scans
for.
Two review findings whose earlier fixes had not landed: the quoted
identifier was still built inside the columnPredicate call, and the
per-column predicate was still bound as `one` in both the join and the
loop.
Keep the scroll-into-view effect from firing on every render: the call
sites build selectedValues inline, so the effect now keys on the values
rather than the array's identity.

Clear the rows on a failed reload, which the split had dropped, so the
previous page's footer no longer sits under an error banner.

Pass the profiled tables into searchLedger. Building the list there again
skipped the ModL fallback, so Explore ordered a keyless file
alphabetically while the sidebar showed chain order.

Compare blob values by content rather than by length alone: two rows
differing only in same-length blob content both highlighted.

Also pass bodyKey so ledger navigation animates as Insights does, merge
LedgerErrorBanner's className instead of replacing it, move a doc comment
that stayed behind in schema.ts, drop one that stayed behind in
RawTablesView, and name the two values in the row comparison.
The pane wrote its own centred, muted div for the "no card selected"
state. LedgerDetailsPane and LedgerTab already render the shared
EmptyState for the same thing, so this was the last of the three copies
the ledger style review named.

Drop the div's p-5 and text-center rather than passing them through
className: EmptyState centres with flex already, and neither ledger site
adds padding, so all three placeholders now look alike.
Move the ledger out of the playground and into a shared
src/s2dm/templates/ledger-ui workspace, alongside insights-ui, so the
scaffolded Docusaurus site can render it too. The data layer becomes
ledger-ui/data, the slices and sagas ledger-ui/state, and the portable
components ledger-ui/components; the playground keeps only its own
shell — the file list, the help button and the collapsible details pane.

Follow insights-ui's host contract rather than inventing one. Selectors
read a private LedgerRootState that each host's larger RootState
structurally satisfies, components use ledger-ui's own typed hooks, and
the fourteen things the shared code still needs from its host arrive
through the @/ alias, which resolves per host. docs-website gains its
own copies: nine ui primitives, an axios-free getErrorMessage, a
useTheme that reads Docusaurus's colour mode, and a Monaco editor
deferred behind BrowserOnly because every route here is prerendered.

Two host differences needed real seams. sql.js cannot resolve its own
wasm URL under two bundlers, so configureSqlJs takes it from the host —
Vite's ?url import in the playground, static/sql-wasm.wasm on the docs
site, copied out of node_modules after install. And openLedger now
carries { name, bytes } instead of a File, so a host that ships a
ledger can dispatch the same action as one that opens a file picker.

The ledger reaches the site the way the composed schema does: staged in
dist/ledger.db, turned into static/ledger.db by npm run doc, and
supplied either by the reusable workflow's new ledger_source input or by
preview-docs.sh. A model without a ledger still builds.

Three Docusaurus CSS gaps had to be closed for the shared components to
render. Infima styles every table on the site from unlayered rules that
utility classes cannot outrank; the site loads Tailwind's theme and
utilities but not its preflight, so a display utility beat [hidden] and
Radix laid out all three tab panels at once; and Tailwind only scans a
project's own directory, so both hosts need an @source line pointing at
each shared template.
Reading the file moved out of the saga in the previous commit, which
dropped its error path: useFileImport calls onFilesSelected without
awaiting, so a failed read became an unhandled rejection and the import
banner stayed empty. Chain the read instead, report failures through
openLedgerFailure, and keep a sequence ref so a superseded pick is
discarded rather than winning by finishing last.

Scope the docs site's portalled content by having it carry the class
rather than the stylesheet reaching for every [role="dialog"] on the
site, which would have stripped an adopter's search modal. Release the
ledger when leaving the page: the database sits decoded in wasm memory,
so the store now hands back a stop() alongside it. Declare
tw-animate-css, which the copied dialog and select assume and the site
did not have.

Unify the danger tone (A19), now that both hosts sit in one layer:
border-destructive/40 in all four files, with the two status-banner
copies kept byte-identical.

Write the tab-close rule once (A8b). Not as extraReducers as the review
proposed — setWorkspace and setExploreTab live in the playground while
both detail slices are templates, so neither can react to them — but in
appSaga, leaving the tab handlers to dispatch only their state change.
Move the view switcher from a centered tab bar into the site's own docs
sidebar, the way the insights page does it. That means a route per view,
so the plugin now loops over a list of sidebar pages instead of
hardcoding insights, sidebars.ts gains a ledgerSidebar, and the page
moves to src/ledger/LedgerPage.tsx behind the same
DocsSidebarProvider/DocRootLayout providers. The file-routed
src/pages/ledger.tsx goes, since it would collide with /ledger.

Add a Schema view carrying what the playground keeps in its left pane:
the table and column summary and the relationships diagram button, held
to a readable column rather than stretched across the page.

The sidebar drives the view by URL while the store switches it from
inside the app — "show in table" does — so the page reconciles them:
whichever side moved since the last check wins. The session moves to
module scope because each view is its own route and the page remounts on
every click; it owns the download so switching view mid-load neither
cancels it nor starts a second one, and reports failure through
openLedgerFailure so the message outlives the switch. It is released on
leaving the ledger rather than on unmount.

Import monaco through edcore.main rather than the package entry: that
drops the TypeScript, JSON, CSS and HTML language services and the other
grammars a SQL box never uses, about 560 KB, while keeping the editor's
own contributions.
Most of these are the missing Tailwind preflight showing up again.
Borders carried a width and a style but no colour, so every uncoloured
border fell back to currentColor and drew in the text colour — the
playground sets border-border in its own base layer, and now so does
this. The popover tokens were absent entirely, leaving the select menus
with no background to resolve; and the table reset that cancels Infima's
row stripes is unlayered, so it beat the row's own background classes
and erased both selection and hover. Both are restated where they can
win.

Put the view switcher in the site's own sidebar order — Schema, Raw
Tables, Explore, Query — with Schema on /ledger so the first entry is
not a dead link.

Strip the details chrome: this host shows details in the document flow
beneath the workspace, where there is no pane to close and the page's
own scroll is the way back, so the title, the back arrow and the close
button go. When nothing is selected a centred line says so, and neither
it nor the details appear under the schema view, which has nothing to
select.

Skip the ledger context for a query projection. It is not a record of
any table, so no chain is ever resolved and the section waited on
something that never arrived.

Bring the details pane to the size insights uses — it was text-xs
throughout against sixteen text-sm — and give the row selection a tint
that reads on white; --accent is a 3% difference there, having been
picked against a dark ground.

Open dialogs below the navbar rather than centred over it: they ask for
90vh, which tucked their top edge behind sticky chrome. Scroll areas
take the site's own thin-scrollbar treatment, which everything outside
the ledger already had.
…l roots

Details sit below the workspace on this host, so following "Show in Raw
Tables" left the grid above the viewport. The grid does scroll the
selected row into view, but with block: "nearest", which is satisfied as
soon as the row is visible inside its own overflow box — the page is
already "nearest" by then and never moves. Correct in the playground,
where the grid fills a pane that is always on screen. The page now
scrolls the workspace in itself, keyed on the rows: the action clears
them through resetTableView and the saga loads a fresh set, so it fires
even when the record is already in the table on screen, which keying on
the table or the page would have missed. Also "nearest", so it stays a
no-op whenever the grid is already visible.

The border defaults reached descendants but not the scope roots. Radix
portals the dialog, its overlay and the select content to the body, so
those carry the class themselves rather than sitting inside it, and
their own border utility was still drawing in currentColor.

Also correct the details shell's comment, which explained the missing
pane rather than the missing controls: following a chain node or a
record action pushes onto the detail stack with nothing to pop it, so
the stack is one-way here. That is the intended trade for keeping the
header off, and the comment now says so.
Four tables were sharing the grid's bounded box, so the last one sat
behind an internal scrollbar that read as the card ending. Only a grid
of many rows needs to scroll inside a box.
@radiachkik

Copy link
Copy Markdown

We need to adapt the workflow to fetch the ledger from the releases instead of the repository itself

Let the three search toggles combine. SearchOptions carried a single
mode, so whole word and regex were mutually exclusive by construction;
they are three independent flags now, and a pattern asked for as both is
wrapped in the same lookarounds the whole-word mode already used. Also
tint the active toggles like the grid rows and record values — they used
--accent, which is barely a shade against a light ground.

Pluralise "See N more bindings": the label is the table name, always
plural, so a single hidden row read wrong.

Give the schema view its own space. The Records line gains the border
its table rows already had, the content fills its card instead of
sitting capped at 40rem in the middle of one, and the relationships
diagram moves out of the dialog into a card of its own below — extracted
as LedgerErdDiagram, which scales to the width it is given. The
playground keeps the dialog, since its overview lives in a narrow pane;
LedgerOverview takes the affordance from its host and null now means
"leave it out" rather than falling back to the button.
SQLite cannot be interrupted, so a query ran to the end on the thread
painting the page. The database now lives on a worker, and Cancel or a
30s deadline ends that worker and reopens the ledger behind it, carrying
over any other work in flight. Typing in the query box no longer redraws
the results with it.

The context tree keeps the record it was built for, past both the page
limit and the node budget and under its own parent. A query result is a
stored record only when the table holds one like it, and references read
positional cells. An empty ledger says so rather than reading as still
loading, and a loaded one survives a failed import.

Each page's sidebar links, routes and rendered view read one definition.
CI now scaffolds a site, type-checks it and builds it, and the template
guide describes how the ledger reaches it.
@barisakcam
barisakcam marked this pull request as ready for review September 15, 2026 14:05
The schema view showed the overview and the diagram stacked on one page,
which made it the longest view and gave the diagram no address of its own.
Give each a sidebar entry under a Schema category, and put that category
last, after the views that read the ledger's records.

Raw Tables takes the root in its place, since the sidebar's first entry is
where /ledger now lands. Structure and Diagram sit under /ledger/schema, so
the two halves share a prefix.

A view carries the section it belongs to, and consecutive views sharing one
become a category, so the order in LEDGER_VIEWS stays the only thing that
decides what the sidebar shows.
The reduce mixed two styles: two branches returned a new array while the
third pushed into the accumulator. Build it in place throughout, which
drops the copying and leaves one return, and name the entry it looks back
at rather than calling it last.

Name the schema-view check once on the page instead of computing it at
both sites that branch on it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants