LiteShip is constraint-based adaptive rendering — a multimedia-native UI compiler/runtime, not a component library. Define adaptive behavior once, apply its attributes and compiled plan to host markup, and inspect exactly why a state won. The same lowered definition remains available to CSS, GPU, ARIA, TypeScript, AI, and video projections, so the approachable front door does not create a second semantic system.
Your UI only needs a few states out: mobile/tablet/desktop, light/dark, reduced/full-motion. But the world feeds it continuous signals — viewport width slides as the user drags, network latency wobbles, the dark-mode toggle fires at 11pm whether the user clicks it or the OS does it for them. LiteShip turns those continuous signals into a small set of named states, and projects each state to whatever surface the host runs.
LiteShip — distributed as @liteship/* packages on npm, installed through the one liteship facade. The naming and prose register the deeper docs use (signal, boundary, cast, surface) lives in GLOSSARY.md — you don't need it for the quick start.
Compared to hand-rolled responsive CSS or a component library: LiteShip owns the adaptive state machine and proves projections from one content-addressed definition — you do not re-sync breakpoints across CSS, GPU, and ARIA by hand. Compared to general UI frameworks: it is not a widget zoo; the host owns markup, LiteShip owns adaptive intent and its projections.
This is a real pre-1.0 framework being hardened on dogfooded sites and a CRM UI.
pnpm: pnpm add liteship (the one-dependency facade) or pnpm create liteship (starter). Package docs: packages/core/README.md · onboarding: GETTING-STARTED.md.
defineAdaptive(...) ─▶ attrs() + plan() ─▶ explain(value)
- define — describe the input, named states, and outputs once
- apply — spread
attrs()onto host markup and use the CSS fromplan() - inspect — call
explain(value)to see the selected state, thresholds, provenance, and identity
- add adaptive states to a page → GETTING-STARTED.md
- author tokens / styles / themes → AUTHORING-MODEL.md
- compile one definition to CSS / GPU / ARIA →
examples/03-cast-aria· PACKAGE-SURFACES.md - let AI propose UI changes safely →
examples/05-ai-patch-refused - accept validated edits back from the client →
examples/06-mutation-roundtrip - deploy on Astro / Cloudflare → HOSTING.md · ASTRO-RUNTIME-MODEL.md
- work on the engine itself → ARCHITECTURE.md · CONTRIBUTING.md
examples/05-ai-patch-refused — a model proposes an invalid GraphPatch, LiteShip refuses it, and the graph stays byte-identical. The model can suggest; only a validated proposal can change the truth.
The examples/ directory has a runnable app per surface. They're workspace members — they consume @liteship/* via workspace:*, so they run from a clone of this repo: pnpm install at the root, then cd examples/<name> && pnpm dev (each example carries its own dev script).
| Example | What it shows |
|---|---|
tutorial |
The guided five-page intro: boundaries → tokens → themes → streaming → LLM/genui |
showcase |
The cast family in one app — CSS/GPU boundaries, workers, streaming + generative-UI |
03-cast-aria |
One boundary cast to CSS and ARIA from a single @quantize block — define-once-cast-many, for accessibility |
05-ai-patch-refused |
The AI-safety seam made visible — an invalid model GraphPatch is refused; only a validated proposal changes the graph |
06-mutation-roundtrip |
The client→server round-trip via createGraphMutationClient + bindGraphForm — the server validates + applies (stale-base patches refused with auto-recovery); the return leg of the stream |
default |
The minimal npm create liteship starter |
cloudflare-astro |
Edge KV boundary cache + Astro middleware on Cloudflare |
remotion-demo |
Headless video export from the same DocumentGraph (standalone: cd examples/remotion-demo && pnpm install) |
examples/scenes/ is a shared fixture directory (test assets like intro-bed.wav), not a runnable workspace.
For a standalone app — one you can drop into StackBlitz or CodeSandbox — scaffold with npm create liteship (also pnpm create liteship). Unlike the workspace examples, the scaffold pins the published liteship facade, so it installs anywhere from npm. In astro dev, open the boundary inspector from the Astro dev-toolbar (the liteship toolbar icon).
The paved road has three moves: define, apply, inspect. In an Astro project:
pnpm add liteshipliteship is the one-package facade over the whole stack: the authoring verbs import from the liteship root, and host surfaces ride domain subpaths like liteship/astro — one package to install, one import path to learn. The root authoring API is synchronous (.read() / .subscribe() / plain function calls) and runs on LiteShip's native substrate (the effect peer was shed in v0.18). Host-specific subpaths keep their real host peer contracts explicit: for example, Astro/Vite integrations require compatible Astro/Vite hosts, and the Remotion package requires React and Remotion. pnpm create liteship scaffolds a starter already wired this way.
Define the adaptive behavior in one root import:
// src/adaptive.ts
import { defineAdaptive } from 'liteship';
export const layout = defineAdaptive({
boundary: {
input: 'viewport.width',
at: [[0, 'mobile'], [768, 'tablet'], [1280, 'desktop']],
},
style: {
base: { properties: { display: 'grid', gap: '1rem' } },
states: {
tablet: { properties: { 'grid-template-columns': 'repeat(2, 1fr)' } },
desktop: { properties: { 'grid-template-columns': 'repeat(3, 1fr)' } },
},
},
});Register the Astro integration once (the scaffold already does this), then apply and inspect the definition:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import { integration } from 'liteship/astro';
export default defineConfig({ integrations: [integration()] });---
import { layout } from '../adaptive.js';
const plan = layout.plan();
const preview = layout.explain(940);
---
<main {...layout.attrs()}>
At 940px the selected state is {preview.boundary.state}.
</main>
<style is:inline set:html={plan.css}></style>Resize the window and watch data-liteship-state change. attrs() carries the runtime definition, plan() returns the compiled CSS and matching attributes, and explain() reports why a state won. The lower-level defineBoundary, defineStyle, compiler, and adaptiveAttrs APIs remain available as explicit escape hatches; GETTING-STARTED.md introduces them only after this route works.
The pattern LiteShip absorbs is the one most projects re-implement, in pieces, by hand:
- the breakpoint table for layout (CSS media queries)
- the theme switcher for colors (CSS variable rebinding plus a JS toggle)
- the ARIA attribute that mirrors the layout state (separate hand-write, easy to forget)
- the GLSL uniform that mirrors the same state on hosts that ship a shader
- the TypeScript discriminated union that lets the rest of your app
switchon the same state
Hand-rolled, those drift the moment one of them falls behind. Authored as a single boundary, they emit from one definition every time. The boundary is content-addressed (FNV-1a + canonical CBOR per ADR-0003); change the definition, every output recomputes against the same hash.
This is not a replacement for media queries (use them where they're enough), CSS custom properties (use them where you control the keyspace), or design-token systems (LiteShip is one, and also the projection layer above them). It's the layer that ties them together so they stay in agreement.
LiteShip's primary host integration is Astro 7 (@liteship/astro). The core authoring layer (@liteship/core, @liteship/quantizer, @liteship/compiler) is framework-portable: it produces CSS strings, GLSL preambles, ARIA records, and TypeScript unions from boundary definitions, and any framework can spread those onto its own elements. @liteship/vite plugs the same @token / @theme / @style / @quantize CSS transforms into any Vite-based stack (React, Solid, Svelte, Vue, vanilla). The Astro-specific surfaces (the Adaptive component, client:adaptive directive, liteshipMiddleware) are additive — you don't need them to use the authoring + casting layer.
Mobile and PWA: viewport, motion-preference, GPU tier, and network-condition signals all flow through the same boundary primitive. The framework is presentation-focused and doesn't ship offline-first / service-worker / manifest tooling; pair LiteShip with whatever PWA stack your host already uses.
LiteShip can adopt one surface at a time; existing CSS keeps working beside it. The liteship/migrate subpath lowers supported media queries, container queries, W3C DTCG 2025.10 tokens, Tailwind @theme blocks, and CSS custom properties into ordinary LiteShip definitions. Each adapter returns diagnostics for unsupported or lossy input rather than silently widening it. Migration is an explicit conversion step, not a compatibility runtime: inspect the result, resolve every diagnostic, then own the emitted LiteShip definitions.
| Dimension | Tier-1 (CI-gated) | Tier-2 (best-effort) |
|---|---|---|
| OS | Windows + Linux + macOS | — |
| Shell | PowerShell + bash | zsh / bash on macOS |
| Node.js | 22, pnpm 10 | same — no known gap |
| Vite / Astro | 8 / 7 | same — no known gap |
| Browsers | Chromium + Firefox + WebKit | same — no known gap |
No Effect runtime; host peers stay explicit. The root authoring layer produces plain CSS strings, GLSL preambles, ARIA records, and TypeScript unions through a synchronous API (.read() / .subscribe() / plain function calls). The effect runtime that earlier previews carried was fully removed (see traceability/effect-shed-receipt.json, ADR-0042 / ADR-0043). Optional or host-specific surfaces still declare the peers they truly execute against — including Astro, Vite, fast-check for the harness subpath, and React/Remotion for video integration — so consumer dependency validation matches the selected surface instead of making a repository-wide zero-peer claim.
Every push and pull request runs the full release candidate before merge. Affected Linux/Windows/browser jobs remain fast feedback, while truth-linux-parallel, browser-e2e, windows-smoke, macos-smoke, macos-browser, rust-wasm-parity, and security-audit prove the exact candidate tree. The main-push run repeats that same fold as confirmation; it is never the first place a release defect is allowed to surface. Scheduled, manual, and release-tag runs add serial and exhaustive mutation/MC/DC authority. The authoritative event-to-job mapping lives in scripts/lib/ci-authority.ts and is executed by .github/workflows/ci.yml.
The event-authority census covers format, pr-affected, pr-windows-affected, conditional pr-browser-affected, truth-linux-parallel, serial truth-linux, browser-e2e, windows-smoke, macos-smoke, macos-browser, rust-wasm-parity, security-audit, exhaustive-analysis, exhaustive-mutation, exhaustive-mcdc, and semantic-assurance-admission.
macOS is tier-1 release authority. macos-smoke and macos-browser run on pull requests and pushes and participate in the final CI fold. Platform-specific path aliases, package-manager behavior, browser execution, and package smoke therefore block before merge instead of becoming post-merge advisory evidence.
DOCS.md is the full map — the shortest route to the right document for humans and agents (reading paths by role, plus a discovery index of where each answer lives in the source). Start there. The essentials:
- GETTING-STARTED.md: install in your project, first boundary, end-to-end
- AUTHORING-MODEL.md: definition shapes, naming, and composition rules
- ARCHITECTURE.md: package and system architecture, the IR, the AI cast
- PACKAGE-SURFACES.md: which package to import for which job
- GLOSSARY.md: LiteShip /
@liteship/*naming and prose register - CONTRIBUTING.md · SECURITY.md · RELEASING.md: dev environment, security posture, release process
Hosting, the Astro mental/runtime models, the ADRs, generated API reference, and the changelog are all routed from DOCS.md.
Trust is set explicitly, not by permission default.
- Runtime endpoints stay same-origin unless you set an allowlist for cross-origin paths.
- Artifact IDs are validated as single path segments. No smuggled traversal.
- LLM rendering defaults to text-safe; HTML flows route through the shared trust pipeline (
text/sanitized-html/ explicittrusted-html). - The runtime carries no
evaland nonew Function. Untrusted text never becomes executable JavaScript at runtime. (WASM bytecode does run at runtime, sandboxed by the host's WASM runtime; seepackages/core/src/wasm/wasm-fallback.tsfor the no-WASM path.) - The Astro integration publishes a frozen
__LITESHIP_RUNTIME_POLICY__snapshot for runtime endpoint and HTML trust decisions.
Full posture and trust-boundary detail in SECURITY.md and STATUS.md.
LiteShip is intentionally not, in the current wave:
- a built-in auth / session stack
- an ORM / storage / queue stack
- a general RPC / server-action framework — the mutation client and form binding still submit one validated graph-patch seam, not arbitrary remote calls
- a backend / router stack
- a stateful edge AI runtime substrate
Pre-1.0 break policy is aggressive on purpose. If an API or internal contract is going to be painful later, the preference is to break it now while the framework is still in greenfield fit-out.
Dev Container: reopen in .devcontainer/ for the same Node 22 + pnpm 10 +
ffmpeg (libx264) + Playwright stack CI uses on ubuntu-latest.
pnpm install
pnpm verify # first-run aggregate: doctor → install → build → test
# ...or step through it yourself:
pnpm run doctor # preflight environment check (Node, pnpm, build, hooks)
pnpm resources:plan # current workstation/load admission
pnpm preflight --staged # resource-aware static proof for the staged packet
pnpm test <focused path> # the packet's smallest deterministic proofDev-loop ergonomics: pnpm dev (showcase example dev server), pnpm test:watch (vitest watch), pnpm run clean (wipe build/test artifacts), pnpm scripts (categorized script index), pnpm run glossary <term> (CLI lookup into the ontology), pnpm fix (prettier + eslint --fix). The CLI mirrors the same surface: liteship doctor, liteship help, liteship version, liteship glossary cast.
Other lanes (test:vite, test:astro, test:tailwind, test:e2e, test:e2e:stress, test:e2e:stream-stress, test:redteam, package:smoke, bench, bench:gate, bench:reality, coverage:merge, report:runtime-seams, audit, report:adaptive-scan, feedback:verify) are documented in CONTRIBUTING.md.
pnpm run gauntlet:full is the full release-grade gate. It runs the complete ordered phase sequence from packages/cli/src/gauntlet-phases.ts, starting with an enforced environment preflight and ending with flex:verify, or it fails closed. It is CI/frozen-head authority—not an ordinary local edit loop. Pull requests first run the conservative affected closure; the final head receives one complete cloud authority.
Pulled from the truth-artifacts-linux artifact of CI run 28506238242 (commit 7d45793) on 2026-07-01 (linux x64, Node 22). Refresh with pnpm exec tsx scripts/refresh-bench-snapshot.ts against a newer artifact, then pnpm run docs:gen.
pnpm run gauntlet:fullpassed end-to-end in 15m42s under CI conditions.bench:gatepassed: 7 hard gates, 0 failed, 5 replicates.package:smokepassed for every publishable@liteship/*scope.
| Hard-gated pair | Median directive | Median baseline | Median overhead | Threshold |
|---|---|---|---|---|
adaptive hot path |
2,699ns | 2,612ns | 3.33% | 15% |
stream parse + patch |
776,063ns | 788,187ns | -1.54% | 15% |
llm text chunk parse |
823,421ns | 775,031ns | 6.20% | 15% |
worker fallback eval |
2,982ns | 2,832ns | 5.33% | 15% |
llm-startup-shared |
76,604ns | 75,313ns | 1.96% | 25% |
llm-promoted-startup-shared |
244,095ns | 251,687ns | 2.58% | 25% |
worker-runtime-startup-shared |
1,075ns | 3,553ns | -69.88% | 25% |
Diagnostic watch, not a release gate: llm-runtime-steady runs above its relative baseline (p99 ratio 34.93x), but the absolute directive p99 is 380,064ns against a 1,000,000ns steady-state budget — ~38.0% of budget, headroom not panic. The live ledger is the truth-artifacts-linux CI artifact (benchmarks/directive-gate.json, gauntlet-phase-timings.json); see STATUS.md for the operator view.
For run-by-run truth, use artifacts produced by the authorities on one frozen source head. STATUS.md names those authorities and deliberately does not claim that an older artifact proves the current branch. Generated artifacts in coverage/, benchmarks/, and reports/ are evidence only when their source SHA matches and the corresponding authority completed.
ARCHITECTURE.md, the ADRs, and the package surface docs explain shape and intent. They are not run-by-run ledgers.
The gauntlet emits UTF-8 (vitest reporter glyphs, JSON receipts). zsh paste trap: interactive zsh does not treat # as a comment unless setopt interactivecomments; inline comments with parentheses (e.g. (libx264):) can glob-fail before the command runs — paste one command per line. PowerShell's > redirect writes UTF-16 LE by default, and viewers using cp437 then mis-render unicode arrows (↓, ✓) as Γåô / Γ£ô mojibake against the repo's UTF-8 stream. To capture clean logs:
pnpm run gauntlet:full | Out-File -Encoding utf8 .log
# or set the codepage once per session:
chcp 65001Bash and PowerShell-on-Linux are unaffected.