Skip to main content

@nzip/lofi · local-first meta-frameworkv0.4.0 · alpha · MIT

Put down roots. Skip the spinner.

lofi generates an Astro + Preact app backed by durable local data. Users start with a private, on-device account, no sign-in or network, and can add Jazz-powered sync and recovery later, without replacing that identity or rewriting product code.

deno run -A jsr:@nzip/lofi/create my-app

Requires Deno 2.9+.

A phone containing a growing plant. Its roots run below the soil line inside the device; a dotted line arcs to a second, smaller phone.

Open now. Back up later.

First launch

A private account, on the device

Every user gets an on-device account at first open, even when the app has no sync service configured at all. Nothing to sign up or waiting.

Every launch after

Reads from local storage, not the network

The UI reads durable local data instead of waiting on a round trip. When storage fails, lofi surfaces it rather than quietly falling back to memory and risking the data.

If the user wants it

The same account, made portable

Connect managed Jazz sync and a user can back up the account they already have — recovering it with a passkey where supported, or a 24-word phrase. Data made offline comes along.

deno task jazz:provision
or scaffold with --sync from the start.

at the core of the idea

Pull the cable.

This is the starter task app the generator gives you. Turn the network off, keep typing on both devices, then turn it back on. Nothing is lost and nothing conflicts. That is what the CRDT buys you.

Synced

This phone

  • Water the ferns
  • Repot the monstera
  • Order more perlite

Your other device

  • Water the ferns
  • Repot the monstera
  • Order more perlite

A simulation, drawn to the same model (not a live Jazz session). For the real one, and for the part where you close the laptop and it is all still there tomorrow: deno task dev.

The parts you would otherwise write yourself.

Local-first data

The UI reads from durable local storage instead of waiting on the network. Storage failures are surfaced, never silently swapped for memory.

Identity from first launch

Each user begins with a private on-device account, even when the app has no sync service configured.

Optional sync and recovery

Users make that same account portable when you connect managed Jazz sync: a passkey where available, a recovery phrase as the portable fallback.

Narrow collaboration templates

Private resources, direct shares, and fixed-role groups sit behind a small @nzip/lofi/access API. Raw Jazz permissions stay available as an escape hatch.

Local-first test helpers

Playwright-backed fixtures cover offline writes, multiple clients, convergence, and readiness — without hand-timed sleeps.

Explicit mobile support

Unsupported browsers get a clear explanation, not a half-working app that risks the user's data.

You own the product.
lofi owns the plumbing.

my-app/
├── deno.json                    # tasks + one pinned version
├── public/                      # manifest, product icons
├── src/
│   ├── app.ts                   # storage, sync, passkey config
│   ├── schema.ts                # persisted data model
│   ├── permissions.ts           # private, shared, or group
│   ├── islands/                 # interactive Preact UI
│   ├── layouts/                 # document shell
│   ├── pages/                   # Astro routes
│   └── styles/                  # product styling
├── tests/                       # local-first journeys
├── .lofi/                       # generated tooling (ignored)
└── dist/                        # production PWA (ignored)

@nzip/lofi                       # storage, identity, sync, PWA

A generated project keeps your source separate from the versioned framework package. Product work stays in the files above; framework behaviour is imported, never copied into your tree. Upgrading the package updates the runtime without a migration through your own source.

  • Yours. Schema, permissions, config, routes, islands, styles, tests.
  • lofi's. Storage, identity, sync, lifecycle, and PWA plumbing — imported from one pinned version.
  • Generated. Regenerated on demand and git-ignored. Never edit by hand.

See the exact generated-project map →

stack and version policy

Pinned on purpose.

The data layer is an alpha, so every upgrade is reviewed and validated before it reaches a generated project.

LayerChoicePinned at
Data / syncJazz 2, CRDTs, OPFS2.0.0-alpha.53
UI runtimePreact islands, thin adapterPreact 10 / Astro 7
ShellPrerendered Astro, static hostAstro 7
ToolchainDeno tasks, npm compatDeno 2.9

What it isn't, yet.

Built for mobile web apps where offline is a requirement, not a best-effort enhancement. Before you commit:

  • It is an early alpha release. Expect the surface to move.
  • The data layer is Jazz 2 alpha, deliberately pinned to a reviewed version.
  • Browser floors are real. Android Chrome 148+ and iOS Safari 16.4+.
  • Recovery is user-controlled. lofi keeps no recoverable account material on the server, so the phrase has to be kept safe.

Every project, a unified toolset.

Every generated project exposes these as deno task <name>.

Everyday development
devRuns the Astro dev server and prints runtime state.
doctorChecks readiness without printing config or secrets.
testRuns the deterministic local-first suite.
buildCreates a static production build in dist/.
previewServes the production build locally.
Sync, schema, deployment
jazz:provisionCreates a managed Jazz app and configures .env.
schema:validate
schema:deploy
Validates and publishes the Jazz schema.
migrations:create
migrations:push
Authors and pushes schema migrations.
deploy
deploy:create
Hosts the static build on Deno Deploy.