Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 99 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,56 @@
<img src="branding/logo-icon.svg" alt="" width="80" height="80" />
<img height="80" width="80" src="branding/logo-icon.svg" />

# Selfie - distributed Storybook testing (and agent control surface)

Visual screenshot testing is an excellent control surface for directing coding agents. Using renders inside the coding loop can increase render counts by 100x to 1000x, which is wildly unaffordable with existing centralized render services such as Chromatic.

Selfie rethinks this model:
Selfie is a visual review gate built for that loop. It runs on your machine beside your agent, in CI, and on GitHub, and it's piloting with StorybookJS. PRs to support other testing models are welcome.

- coding models can (and should!) generate renders locally to validate and document their work
- this local work can (optionally) be trusted to dramatically reduce cloud render usage (\~100x faster and cheaper)
- agents and humans should be able to directly annotate the renders and their deltas, during local development and cloud review
## Watch it

Selfie is piloting with StorybookJS, but PRs to support other testing models are welcome.
<table>
<tr>
<td width="50%" valign="top">
<a href="https://youtu.be/8B3-v4DTwfU">
![Watch: Why Selfie](https://img.youtube.com/vi/8B3-v4DTwfU/maxresdefault.jpg)
</a>

A hosted instance batches at [hackathon.selfie.dev](https://hackathon.selfie.dev).
<br />

<strong><a href="https://youtu.be/8B3-v4DTwfU">Why Selfie</a></strong>

<br />

From Storybook to Chromatic to Selfie: why snapshot testing needs to change for coding agents.
</td>

<td width="50%" valign="top">
<a href="https://youtu.be/FiGuwfB13b0">
![Watch: Selfie product demo](https://img.youtube.com/vi/FiGuwfB13b0/maxresdefault.jpg)
</a>

<br />

<strong><a href="https://youtu.be/FiGuwfB13b0">Product demo</a></strong>

<br />

The real app: a two-line CSS change touches all 124 stories, and a person and an agent work through it together.
</td>
</tr>
</table>

You can also look around the hosted review pages without signing in: here's [Selfie reviewing itself](https://hackathon.selfie.dev/diffplug/selfie-hackathon).

## What it does

- **Fits an agent's loop.** `selfie review` prints one JSON document naming the diff images, with an exit code that says whether anything blocks.
- **Runs where the agent runs.** On an M4 MacBook Air, Selfie's own 124 stories render in about 25 seconds, with no cloud and no bill per image. A larger Storybook of 541 stories takes about 70 seconds. Reviewing a build that's already rendered takes under half a second.
- **Says what kind of change it is.** Selfie separates what moved or gained spacing from what really changed, so you can approve the harmless ones together.
- **Lets you point at the problem.** Write a note on an image, and your agent gets your words, the exact box, and a picture of what you saw. It answers on the same note, and you flip between before and after.
- **Follows Git.** An approval counts wherever history reaches it, so it isn't repeated after a commit, a rebase, or a merge.
- **Gives the same pixels everywhere.** Stories render in a pinned container, and `selfie flakes` finds any that still vary between renders.
- **Reports on every commit.** A GitHub Action renders each push, and the commit gets a `selfie` status that links to its review.

## Quickstart

Expand All @@ -30,66 +68,89 @@ It works out how your Storybook builds, writes `selfie.config.json`, proves that

You'll need Node 26 or later, pnpm, and Docker. Selfie isn't on npm yet, which is why this starts from a checkout.

## Built for the Meta Global AI Developer Hackathon
## How it works

We previously launched [Selfie](https://lib.selfie.dev), a snapshot testing library focused on data rather than screenshots with no cloud or agent integrations. We are backgrounding it to focus on this completely new and unrelated service, which shares the same general theme and name but none of its code. Everything in this repo was built from November 1, 2026 onward for the [Meta Global AI Developer Hackathon](https://cerebralvalley.ai/e/meta-ai-developer-hackathon/details).
**An agent's loop:** `selfie review`, then look at the diff images it names, then `selfie approve <batch> --all`, commit, and `selfie review` again, which has nothing left to ask.

Pre-existing work used here:
**A person's loop** is the same, with `selfie ui` for the looking and deciding.

- [Selfie branding](branding/README.md) (Apache-2.0, DiffPlug): the original SVG marks and mascot illustration, reused from the earlier Selfie library.
- [pgstencil](https://github.com/diffplug/pgstencil) (MIT, DiffPlug LLC), consumed as published npm packages to bootstrap the Postgres backend. None of its source is copied into this repo.
**To work together,** a person leaves notes on any story in `selfie ui`, and either copies them into any agent, or presses Send while the agent runs `selfie agent-await-feedback`. The agent acts on them, runs `selfie render`, and answers each with `selfie reply`. The person flips against the image they commented on, and resolves the note or answers back.

## Where Meta models are used
**In the cloud,** a GitHub Action renders every pushed branch and uploads its batch. Each commit gets a `selfie` status: red if a story failed or a person rejected a change, yellow if a change is undecided, and green if everything matches what's approved.

Every model Selfie calls at runtime is a Meta model.
A project needs a `selfie.config.json` at its Git top level, which says where its built Storybook is and how to build it, and may also set its environment, init script, and how close changed hunks join.

- **Muse Spark** (Meta Model API), in the cloud service: reads the before/after renders together with the code diff and the stated intent of the change (commit message, PR description, or agent prompt), groups the changed renders by the code change that caused them, and flags changes that weren't asked for so they can be approved or rejected in batches.
- **Muse Code**, in the local loop: the coding agent that makes a change, runs `review`, and fixes the regressions Selfie flags. Selfie itself works with any coding agent.
### Commands

These parts are ordinary code, not model calls: rendering renders (Storybook), hashing and pixel-diffing images, walking history with git, and storing data (SQLite locally; in the cloud, Neon Postgres for accounts, a D1 database per project for render metadata, and R2 for image bytes).
From a checkout, `pnpm selfie <command>` runs these.

## System architecture
| Command | Does |
| - | - |
| `render` | Always renders the Storybook, and records a batch |
| `review` | Shows a batch's diff against what its history has approved, as one JSON document whose exit code says whether anything blocks. With no batch, it uses the one for this exact built Storybook, and records one only if there's none yet. `--baseline` compares with what a commit's history approved, or with another batch |
| `approve`, `reject` | Decides on what a batch changed, added, or removed, on any branch, on a dirty tree if you like |
| `ui` | Serves a local review page, to look at a batch's images, approve, and reject. It renders only when you ask it to on the page |
| `selfie`, with no command | Opens every story of a built Storybook, running, to write notes on, which renders what they're on |
| `notes`, `agent-await-feedback`, `reply` | How a person's notes on the images reach a coding agent, and how it answers |
| `push`, `pull` | Sends a batch and its decisions to the cloud, and takes what a commit's history approved from it |
| `flakes` | Renders a built Storybook several times, and says what wasn't the same each time. `selfie skill fix-flakes` is a guide for a coding agent to fix what it finds |

Selfie stores data in two places:
## What's built, and what isn't

The local loop, CI, and commit statuses are all built and in use on this repository.

The cloud service at [hackathon.selfie.dev](https://hackathon.selfie.dev) is deployed for a few public repositories. Anyone can view them, and for now only one person can sign in to approve or upload.

Not built yet:

- a cloud service holding batches and the approvals on them
- local data stored at `~/.config/selfie/<git-origin-slugified>/`, Selfie stores locally generated renders here, and also mirrors data from the cloud service as necessary to improve performance
- **Change grouping with Muse Spark** ([below](#meta-models))
- **Trusting local results,** so that the cloud can accept an agent's renders without rendering them again
- **Signing in through GitHub,** with access that follows GitHub's permissions, and private repositories
- **Notes in the cloud.** Notes are local for now
- **An npm package**

Access follows GitHub through a GitHub App: anyone can view a public repository's renders, and a private repository's are visible only to people with access to it on GitHub.
## Built for the Meta Global AI Developer Hackathon

We previously launched [Selfie](https://lib.selfie.dev), a snapshot testing library focused on data rather than screenshots with no cloud or agent integrations. We are backgrounding it to focus on this completely new and unrelated service, which shares the same general theme and name but none of its code. Everything in this repo was built from November 1, 2026 onward for the [Meta Global AI Developer Hackathon](https://cerebralvalley.ai/e/meta-ai-developer-hackathon/details).

Selfie has the following commands:
### Meta models

- render - always renders the Storybook, and records a batch
- review - shows a batch's diff against what its history has approved, as one JSON document whose exit code says whether anything blocks. With no batch, it uses the one for this exact built Storybook, which takes about 0.3 s rather than 70 s on 541 stories, and records one only if there's none yet. `--baseline` compares with what a commit's history approved, or another batch, so recording twice and comparing is how to look for flakiness
- approve, reject - decides on what a batch changed, added, or removed, on any branch, on a dirty tree if you like. A later batch whose history reaches an approval needs no repeating it, whether after a commit, a rebase, or a merge
- push, pull - send a batch and its decisions to the cloud, and take what a commit's history approved from it
- flakes - renders a built Storybook several times, and says what wasn't the same each time. `selfie skill fix-flakes` is a guide for a coding agent to fix what it finds
- ui - serves a local review page in the browser, to look at a batch's images, approve, and reject. It renders only when you ask it to on the page.
- selfie, with no command - opens every story of a built Storybook, running, to write notes on, which renders what they're on
- notes, agent-await-feedback, reply - how a person's notes on the images reach a coding agent, and how it answers
**As submitted, Selfie doesn't call a Meta model.** We spent the event building the foundation: rendering, storage, review, and sync.

An agent's loop is `selfie review`, then look at the diff images it names, then `selfie approve <batch> --all`, commit, and `selfie review`, which has nothing left to ask. A project needs a `selfie.config.json` at its Git top level, which says where its built Storybook is and how to build it, and may also set its environment, init script, and how close changed hunks join. A person's loop is the same with `selfie ui` for the looking and deciding. To work together, a person leaves notes on any story in `selfie ui`, and either copies them into any agent, or presses Send while the agent runs `selfie agent-await-feedback`. The agent acts on them, runs `selfie render`, and answers each with `selfie reply`, and the person flips against the image they commented on, and resolves it or answers back. See `docs/specs/feedback.md`. These work on local data, and push and pull are built, but no server is deployed for them yet. See `docs/specs/cli.md` and `docs/specs/ui.md`.
The planned integration is specified in [`docs/specs/system.md`](docs/specs/system.md) as `change-grouping`. It would send Muse Spark the before and after renders, the code diff, and the stated intent of the change, then group the changed renders by the code change that caused them, and flag the changes nobody asked for. Selfie's own code can say exactly which pixels differ and whether content moved, but not why, or whether anyone wanted it. That matters at agent scale: one style tweak we measured changed 290 of a Storybook's 541 stories.

## Implementation details
## Architecture

Selfie stores data in two places:

- **Locally,** at `~/.config/selfie/<git-origin-slugified>/`, in SQLite and image files. Locally generated renders live here, along with what's mirrored from the cloud.
- **In the cloud,** in a Hono app on Cloudflare Workers, with render metadata in a per-project D1 database and image bytes in R2.

Implementation details:

- image renders are fully opaque 8-bit RGB, and transparency is not supported
- image renders are always referred to by the SHA256 hash of the render pixel data in the PAM format
- the db primary key of images is based on this SHA256, truncated to the safe range for positive javascript integers
- images whose truncated hash collides are stored in the same table under monotonic negative integers
- local app is built with SQLite, Typescript and Vite
- local review UI is a Hono app with GitHub Primer, on loopback only
- cloud server is implemented with Hono (typically Cloudflare Workers), Typescript and Vite, with user accounts in Neon Postgres (via pgstencil, connecting through Hyperdrive), render metadata in a per-project Cloudflare D1 database, and image bytes in Cloudflare R2
- stories render with Playwright, in a container pinned by digest
- the local app is built with SQLite, TypeScript and Vite
- the review UI is a Hono app with GitHub Primer, on loopback only when it's local
- commit statuses come from a GitHub App

The full reference is in [`docs/specs/`](docs/specs/). Start with the [glossary](docs/specs/glossary.md).

## Third-party open source

- [Storybook](https://storybook.js.org) (MIT)
- [Playwright](https://playwright.dev) (Apache-2.0)
- [Hono](https://hono.dev) (MIT)
- [Vite](https://vite.dev) (MIT)
- [GitHub Primer](https://primer.style) (MIT)
- [React](https://react.dev) (MIT)
- [sharp](https://sharp.pixelplumbing.com) (Apache-2.0)
- [SQLite](https://sqlite.org) (public domain)
- [PostgreSQL](https://www.postgresql.org) (PostgreSQL License)
- [pgstencil](https://github.com/diffplug/pgstencil) (MIT)

The two videos are built in [`demo-video/`](demo-video/) and [`product-demo/`](product-demo/) with [HyperFrames](https://hyperframes.heygen.com) (Apache-2.0), [GSAP](https://gsap.com) ([standard license](https://gsap.com/standard-license)), and the Bagel Fat One and Jost fonts (OFL-1.1), with voiceover from [ElevenLabs](https://elevenlabs.io).

## License

Expand Down
Loading