Small tools, one thread.

Every app is an independent producer or reader of the same lexicon-shaped records. None of them talk to each other — they only share the schemas — which is why you can use one, some, or all of them. Two are the centre of gravity: Loom, the desk where records are written, and the String, the store that keeps them.

How a moment travels

totem press ───┐ phone mint ────┤ scrobbled play ┤──> Loom (the desk: write it whole, keep proposals) ──> the String hand-written ──┘ │ (your store) │ deliberate publish ▼ your ATProto repo ──> the wall · the catalogue · the strand embed · the AppView

Beads arrive from whatever had your hands at the time. Loom is where they become an entry; the String is where they stay. Publishing is a separate, deliberate press — and what leaves is decided by one strip rule, held to its Python and JavaScript implementations by shared fixtures.

The desk

Loom phase 1b

The whole loop in one page: your String always in view as a column — month calendar, entries newest first, proposals kept or released in place — and an editor beside it where a bead or a strand is written whole. Vanilla ES modules and IndexedDB, no framework, no build step; it works with no server at all. Send carries every change to the String — new, edited, kept, deleted — each against the version Loom last saw, so a record changed on both sides is flagged for you to choose rather than silently overwritten.

My beads new

Your beads, wherever you are, with nothing to install. Sign in with your Atmosphere account and every bead in it is there — from Loom, a totem, I went — to search, put right, delete, or add to. Not a diary and not local-first: it works on your account directly, changing only the fields you touch and refusing to overwrite a bead edited elsewhere since you opened it. Installs as an app on your phone.

CultureBloc Studio retired

The old desk: pull the totem over USB, resolve its elapsed-time counters to real instants, tell the day, push to the store. Retired in September 2026 once Loom took Web Serial and the totem sync. The totem has no network path by construction, so a browser holding the serial port is the only way in — and two apps holding it was one too many. Everything Studio did, Loom does.

Minting — where beads begin

I went new

The front door. Press I went on any event in a directory, write a line, sign in with your Bluesky (or any Atmosphere) account: a visit bead pointing at that event lands in your own repository, with an optional Bluesky post. No app to install — and Loom brings those beads home if you take it up later.

The Totem working

A pocket ESP32 device (M5Stack StickS3). Hold the button to mint a bead wearing your current mask; press together with a friend for a mutual encounter, notarised on both devices. No clock, no network, weeks of battery — it counts elapsed time and Loom resolves that to real instants when you plug it in.

Pocket Totem working

The totem on glass: a mask strip and a mint button rendered as a live dot-matrix. Beads queue locally and go to your String, or — signed in with ATProto OAuth — straight to your own repository with no server of your own. Scanning a venue’s QR attaches that night’s event to what you mint.

Scrobble worker working

Deliberate listening, recorded: Sonos plays scrobble to Last.fm; the worker clusters them into sessions and proposes one listen bead per session. Machine beads arrive with state: proposal, sit on a dotted rail, and may be revised by a later run — until a person keeps one, after which no worker can touch it again.

By hand working

For moments no device witnessed: scripts/mint.py on the command line, or the Claude Code skill, which mints, edits and publishes conversationally from a terminal.

AR Gallery parked

Point a phone at a painting; it resolves the work and mints an annotation. Parked pending a rethink — the reframe when it returns is phone-first and lens-as-gesture, matching on-device against venue-published packs so no images leave the phone. The annotation lexicon waits ready.

Keeping — the String

The String working

A small self-hosted record store — beads on a thread, in the lineage of the quipu. Validates every record against the lexicons, deduplicates, keeps an append-only change feed, serves the timeline, holds your publishing identities. One SQLite file, your disk, one container. (Formerly “the Spine”: renamed, but the API, the data and existing spine:// record URIs are unchanged.)

What Phase 0 changed landed

The foundations a local-first client needs. The lexicon validator and the publish strip were ported to the browser and are held to their Python originals by shared fixtures. Records grew an explicit state, so the dotted rail is stored rather than guessed from an app name. The change feed gained HLC stamps, device and actor. PATCH honours If-Match and removes a field sent as null; DELETE honours it too. A connector re-run can revise its own proposal and can no longer erase a person’s edits to one.

The timeline working

The String’s own day view: annotate, attach photos, set kind, tags, place and links, keep or release proposals, group beads into strands and publish them. Still running, and not retired — Loom is built alongside it, and daily use decides the rest.

The DataBrick spec written

A Raspberry Pi as an always-on host: a physical object in the house holding the household’s cultural memory, and the natural home for a personal sync server. Retires the sleeping-laptop failure class.

Creative identity & venues

Easel working

Claim your work: a browser app for creative.profile and creative.work records, with images, alt text and dimensions. IndexedDB and content-addressed blobs; signs in with ATProto OAuth and publishes to your own repository, tracking drift against what is public. No server involved at any point.

Doors working

The venue app: a profile at rkey: self, and a night published as a shared community.lexicon.calendar.event with our venue.lineup layered over it — both records from one form. Prints a QR per event that opens Pocket with the event attached, so a bead about tonight is one tap.

The audience↔work join working

The part no other system has. A venue puts out an event; people who came publish their own beads referencing it; the venue counts public references. It collects nothing and stores nothing about a person — the evidence is published by the people who chose to publish it. Public references are countable; identities are not the venue’s to collect.

Reading — what published records look like

The wall working

One actor’s published cultureblocs, newest first, one URL per strand. Reads their own PDS in the browser — no index in the middle, nothing of ours between a reader and the record.

Catalogue working

An artist’s published works, read live from the network and rendered through a swappable template stylesheet — grid, index or feature. Point it at a handle and it is a portfolio site with no hosting behind it.

Strand embed working

A dependency-free web component that renders published strands on any static site — live from a repo, or from a baked export. Same markup either way. The meetup page below runs on it.

AppView working

A network index over public records: a Jetstream consumer and a query API turning scattered references into counts. It indexes the shared calendar types too, so it works for venues that have never heard of CultureBlocs — verified against a festival’s published programme. Counts, not dossiers.

Where this goes next

Loom’s remaining phases are the plan of record. Phase 2 makes the String a personal sync server: a discovery record in your own repository so a new device finds your String by signing in rather than by typing a URL and a token; DID-scoped, NSID-scoped grants in place of one shared bearer, so a connector that may propose listen beads cannot read your identities; and a change feed that goes both ways. Phase 3 brings the vault — credentials that sync, encrypted under a passphrase the server never sees — and connectors as manifests, including the resolver that proposes refs. Phase 4 moves publishing into Loom, client-side, so a person with no String at all still has the whole loop.

Nothing is retired on argument alone. Studio went because two apps could not share one serial port; the rest waits on a season of daily use.