S
How a moment works
The moment lifecycle as stored, the components it is built from, and the rules the product keeps.
SystemA
Arrive
From the landing page to your first app, with two moments free.
4 screensB
Make a moment
Describe it, watch it draw, keep it.
7 screensC
Change it
Select a part and say what to change. Colors and motion open in place.
6 screensD
Use it
Copy it into React, see it in a screen, or take the files.
5 screensE
Credits
A moment is 10 credits. Packs are bought with a card or USDC.
5 screensF
Your apps
Each app keeps its moments together, in one style.
3 screens
How a moment moves
Boxes are moment.status values as stored, diamonds are checks, pills start or end. Click a box to open its screen.
Taps DrawA brief and a kind.
Components
Existing is used as is, adapted is an existing component in a new place, new is built for this feature.
Welcome screen after signup. The invoice buddy greets them and invites a first invoice.WelcomeDraw10
ComposerAdapted
src/illustrations/editor/Studio.tsxPrice on the button.
ReplayColorsMotionAnother 1 includedUse it
Control rowNew
new: src/moments/Controls.tsxFour actions, one primary.
Credit chipAdapted
origin/main src/billing/credits.tsBalance, and what is held.
Design rules
How this fits the product.
- One work on screenThe moment is the page. Everything else waits for a tap.
- The price is on the buttonDraw 10, Change 3. No variable charges and no cost read-outs.
- Failures are freeA drawing that fails or is refused releases its credits, and the message says so.
- Select on the artworkParts are picked where they are drawn. Layers are an implementation detail.
- Preview is the exportThe same runtime plays every screen, the download and the customer's app.
- One style per appPalette and character carry from the first moment to the next.
A1Landing
visitor: anonymousOne promise, shown with a real set made for one sample app. The hero plays its celebration on load and on hover.
- Static marketing page on the existing Worker, src/marketing
- Hero artwork is a real moment played by runtime.js, about 6 KB of SVG
1 existing
A2Sign in
auth: email_requestedEmail only. The trial is tied to a verified address so it can't be farmed.
- POST /auth/email (origin/main email OTP)
- Rate limited per address and IP
A3Enter code
auth: code_sentSix boxes, paste-friendly. Verifying grants the free moment.
- POST /auth/verify
- On first verify: ledger grant 20 credits (two moments), reason 'Trial credits'
1 existing
A4Your app
app: created · palette: 4 colors · credits: 20One question sets the context every moment inherits: what the app is and its colors.
- POST /api/apps {name, palette}
- apps(id, account_id, name, palette_json, style_moment_id null)
2 adapted
B1Describe
moment: none · credits: 20Nothing to configure. Three suggestions written for this app show what a moment is.
- Suggestions from the app name, no model call
- Draw stays disabled until the brief has three words
4 adapted
B2Ready to draw
moment: describing · kind: onboarding (inferred)The kind is inferred and shown, so the motion matches the moment. Tap it to change.
- Kind inferred locally from the brief, editable
- Draw 10 is the whole price: one drawing, one alternative, two changes
5 adapted
B3Drawing
moment: drawing · credits: 10 heldNo spinner. Each named part appears as soon as it is drawn, so the wait is something to watch.
- POST /api/moments {app_id, brief, kind} returns NDJSON
- Server holds 10 credits before the first provider call
- Measured: 49 to 60 s and $0.036 to $0.044 per drawing, 3 of 3 accepted (Oct 5)
- Stop cancels and releases the hold
1 existing5 adapted2 new
B4Ready
moment: ready · credits: settled 10One work on screen. It plays its welcome once, then rests. Hover and click behave as they will in the app.
- Hold settles at 10 credits, whatever the provider cost
- moments(id, app_id, kind, brief, status, revision, changes_left 2, alternative_left 1, r2_key)
- Review renders 4 samples before ready, existing studio harness
1 existing4 adapted2 new
B5Another draft
moment: ready · drafts: 2 · alternative_left: 0The included alternative. Both drafts stay one tap apart until one is kept.
- POST /api/moments/:id/alternative, included once
- Measured: second draft $0.040, 78 s (Oct 5); no credits move
1 existing4 adapted3 new
B6Didn't work
moment: failed · credits: released 10Failures cost nothing and say so in the same sentence.
- Released on render failure, review failure, provider error or timeout
- Seen live Oct 5: a provider fetch failed after 10 s with no reported cost
- Provider spend on failures is ours; budget 20% overhead
5 adapted
B7Can't draw that
moment: refused · credits: released 10A refusal names the rule and the way forward, and keeps the brief so it can be edited.
- Model refusal or policy check before any drawing
- No credits held for a pre-check refusal
4 adapted
C1Select a part
moment: ready · selection: buddySelect on the artwork. The change field names the part, and the server rejects edits outside it.
- Selection is a named group id from the SVG
- Existing scoped refinement: input.selection in server/illustrations.ts
1 existing5 adapted2 new
C2Changing
moment: changing · selection: buddy · changes_left: 2The rest of the picture stays put while one part changes.
- POST /api/moments/:id/change {part, request}
- Measured: scoped changes took 50 to 79 s and $0.061 to $0.067 (Oct 5)
1 existing5 adapted2 new
C3Changed
moment: ready · revision: 2 · changes_left: 1The change applies with Undo in the toast, and the count of included changes drops by one.
- Undo restores revision 1 from R2, free
- Revisions are immutable objects, key moments/:id/r/:n
1 existing4 adapted2 new
C4Colors
moment: ready · palette: appColors are roles shared by the whole app, so the set stays matched. No model call.
- PATCH /api/apps/:id {palette}
- runtime.setPalette sets CSS variables, verified live
1 existing5 adapted2 new
C5Motion
moment: ready · behavior: onceMotion is one choice: when it plays. Timing hides behind a single speed control.
- Maps to project.interaction and state durationMs
- No model call, saved as a new revision
1 existing5 adapted2 new
C6Changes used
moment: ready · changes_left: 0 · credits: 10After the two included changes, each one shows its price on the button: 3 credits.
- Hold 3 credits per extra change
- Measured change cost $0.061 to $0.067; 3 credits is $0.15 at the $5 pack
1 existing5 adapted2 new
D1Use it
moment: ready · export: reactReact first, because the buyer is shipping a screen. Exports are free and unlimited.
- GET /api/moments/:id/package returns the zip from src/illustrations/exports.ts
- Files: Illustration.tsx, runtime.js, runtime.d.ts, illustration.chocopie.json, artwork.svg, index.html, README.md
1 existing5 adapted1 new
D2In a screen
moment: ready · preview: in_screen lightThe moment inside a plain screen of the app, to judge size and tone before shipping.
- Client only: the same runtime inside a sample layout
- Transparent background option removes the paper
1 existing1 new
D5In a dark screen
moment: ready · preview: in_screen darkDark mode shows whether the paper background should go. One toggle, no settings page.
- Same runtime, background layer kept
- Transparent export hides the data-background group
1 existing1 new
D3Copied
moment: used · package_downloads: 1Using it suggests the next moment of the same app, which is how a set grows.
- Download is logged for the shipped-asset metric
- Suggestion comes from the kinds the app is missing
1 existing4 adapted2 new
D4Image files
moment: ready · export: imageStatic artwork is one tab over, never the default.
- Existing: SVG, PNG up to 2× capped at 2048 px, project JSON
1 existing5 adapted1 new
The chip explains the only price there is.
- GET /api/account returns balance in whole credits
- Ledger stays in micro-dollar units server side
1 existing5 adapted2 new
E2Not enough credits
moment: describing · credits: 0 · checkout: choosingPaying happens where the need is, with the brief kept. Two packs, priced in moments.
- packs: spark $5 / 100 (10 moments), studio $20 / 500 (50 moments)
- Retire flow $10 / 250 and studio $50 / 1,500 for new purchases; honor balances
1 existing7 adapted1 new
E3Pay
checkout: card_entry · pack: sparkCard first; USDC stays as a quiet option in the same checkout.
- Stripe Checkout or Payment Element: cards 2.9% + 30¢, USDC 1.5%
- Webhook grants credits once per payment intent
- Existing USDC wallet flow kept as the alternative
1 existing4 adapted1 new
E4Credits added
checkout: paid · credits: 100 → 90 held 10 · moment: drawingPayment returns straight to the drawing that asked for it.
- Resume the saved brief after the webhook confirms
- If the webhook is late, show 'Confirming payment' and wait
1 existing5 adapted3 new
E5Card declined
checkout: declinedThe error sits on the field that caused it and offers the other rail.
- Stripe decline codes mapped to one sentence
- No credits move until payment succeeds
1 existing4 adapted1 new
F1Your apps
apps: 1 · credits: 90Home is a list of apps, not a gallery. Nothing public by default.
- GET /api/apps
- Retire public profiles and collections from the product
1 existing3 adapted
F2Tally's moments
app: tally · moments: 3An app's set at a glance, with the next missing moment one tap away.
- Suggestions from kinds the app lacks
- Every tile plays on hover with runtime.js
1 existing2 adapted1 new
F3Matching
moment: describing · style_ref: tally/welcomeA new moment inherits the app's palette and character, so it matches without asking.
- Brief includes the app house style and the first moment's SVG as reference
- Measured: three briefs with one shared house style came back as a matched set (Oct 5)
1 existing5 adapted1 new