max / quasi
git clone https://makenot.work/git/max/quasi.git
git clone git@ssh.makenot.work:max/quasi.git
| Name | Size | |
|---|---|---|
| crates/ | ||
| scripts/ | ||
| .gitignore | 554 B | |
| Cargo.lock | 148.6 KB | |
| Cargo.toml | 2.3 KB | |
| CONTRIBUTING.md | 3.0 KB | |
| deny.toml | 2.3 KB | |
| LICENSE | 1.0 KB | |
| README.md | 7.1 KB | |
| rust-toolchain.toml | 86 B |
README
quasi
quasicoherent: the stack every Make Creative app is built on. quasi for short,
and that is what you type.
A quasi-coherent sheaf is determined by local module data on an affine cover and glues, with no finiteness demanded of the pieces. That is the architecture: one description per host context, agreeing on overlaps, renderers free to be unalike.
What it is
request (path + params)
-> router this repo, imports no host crate
-> description makeover-layout
-> renderer webview | tui | egui
-> host adapter axum route | Tauri protocol | wry | direct call
The router answers with what a screen is. The host decides how that becomes pixels. One codebase reaches desktop, iOS, a terminal, egui, and a hosted web app, because the host is the boundary that stops mattering.
What it owns
Seams, not vendors. quasi owns the boundaries between code we own and the major dependencies underneath it, and the test for admitting one is:
Name two implementations that exist or are already committed to. A boundary with one implementation forever is not a boundary, it is an indirection.
By that test, in scope: the host (Tauri, wry, axum, direct call), the renderer
(webview, tui, egui, via makeover-layout), database configuration and
migration across SQLite and Postgres, and eventually a shared session and auth
layer, which the server has and the desktop apps do not.
Out of scope, deliberately: tokio, serde, tracing, chrono, uuid,
thiserror. Ubiquitous, stable, and with no second implementation anyone would
adopt, so wrapping them costs a translation tax and returns nothing.
Also out of scope: anything whose value is its API surface. sqlx’s
compile-time-checked query macros are the reason to use sqlx, so quasi owns
which driver is configured and how migrations run, and queries stay written
against the driver directly.
The two stores are not one query layer, and quasi does not pretend otherwise.
Embedded is rusqlite and synchronous; hosted Postgres is sqlx and async. A
stack claiming a single query layer would have the scaffolder generate the wrong
one.
Boundaries already owned elsewhere stay where they are and are referenced rather
than absorbed: makeover and makeover-geometry for colour and spacing,
synckit for sync, s3-storage for blobs, docengine for rendered content,
tagtree for tags. One repo per project. quasi names the stack and fills the
seams that have no owner.
Layout
crates/quasi-router/— the host-agnostic router. The keystone.crates/quasi-http/— the seam the http-shaped hosts share: decoding, status mapping, the htmx contract, theServestrait.crates/quasi-axum/— the axum host adapter.crates/quasi-tauri/— the Tauri custom-protocol host adapter.crates/quasi-webview/— the webview renderer: a screen in, an htmx document out.crates/quasi-tui/— the terminal renderer: a screen in, cells in a ratatui buffer out.crates/quasi-immediate/— the immediate-mode renderer: a screen in, an egui frame out.crates/quasi-basics/— the first-party widget set: named assemblies of primitives, shared across our apps.crates/quasi-notifs/— declared notification kinds: what a notification says and what it is for, the configuration that follows from it, and the delivery every host would otherwise re-answer.crates/quasi-store/— the embedded store’s migration runner.crates/quasi-bench/— what a described screen costs, in time and in allocations.crates/quasi/— the scaffolder binary, andtemplate/, the app it writes.
Crates are added when their component starts, not up front.
Getting an app
cargo run -p quasi -- new fieldnotes
cd fieldnotes
cargo test the screens, with no host
cargo run -p fieldnotes-server http://127.0.0.1:3000
cargo run -p fieldnotes-desktop a window
What comes out is a workspace of three crates: -core holds the state, the
store and the described screens and imports no host crate; -desktop and
-server are a window and a listener over the same router. Both serve the same
screens and neither knows the other exists.
The parts
The router carries the contract in full: one address space where the verb
separates a read from a write, a screen tree composed from makeover-layout’s
vocabulary, responses that name the region they replace, and failures classified
so a host can turn one into a status code and another into a banner. It is sync,
because the two renderers shipping first call it inside a frame and an event
loop.
quasi-http is what the http-shaped hosts share. Decoding a query string and a
form body, mapping an error’s class to a status, and naming a fragment’s region
in an HX-Retarget header. The transport
is htmx 4, which is what an emitted document links and what the response headers
here are checked against. Nothing in it knows which host it is in, and nothing
in it is async.
quasi-axum is the hosted adapter. What is left in it is axum’s own: it mounts
as a fallback, so ordinary axum routes merged in front keep serving the things a
description has no word for (static assets, health, file uploads), it reads the
body with a limit, and it makes the blocking hop the sync router needs.
quasi-tauri is the desktop and iOS adapter, a custom-protocol handler. The
window loads from the adapter’s own scheme rather than from tauri’s asset
protocol, which is what keeps every action a screen emits same-origin: no CORS,
nothing added to the CSP. A passthrough closure is its version of axum’s
merged routes. There is no platform branch in it, because wry hands a handler
<scheme>://localhost/<path> on Linux, macOS, iOS and Windows alike.
Neither adapter emits markup. A Serves implementation supplies that, because
both serve HTML to a webview and generating it inside one would guarantee a
second copy. quasi-webview is that implementation: a screen in, an htmx
document out, with the transport entering in a single function.
quasi-notifs is a notification declared once. A kind carries what it says,
what it is for, whether it ships on and what its knobs are; the configuration
follows from the declaration, keys, postures, defaults and a described settings
pane, rather than being written beside it in three places. Delivery is a host
adapter of one method, because everything else a host would answer for itself
(consulting the kind’s enabled key, suppressing a duplicate, staying quiet
about what came due while the app was shut, bounding the memory that takes) is
shared and belongs to nobody’s renderer.
quasi-store is the embedded store’s migration runner. SQL files embedded at
build time, applied once, recorded in a ledger with a checksum, so an applied
migration is immutable.
quasi is the scaffolder. Its template is real Rust and real manifests under
crates/quasi/template/, excluded from the workspace and embedded in the
binary, so it is readable and diffable rather than a liquid dialect that
compiles nowhere.
Design and sequencing live in the wiki note quasi-overview; the backlog is in
GoingsOn under project quasicoherent.
Licence
MIT.