···11+# Writing-first flow — design
22+33+**Date:** 2026-06-17
44+**Status:** Design — approved, pending spec review
55+**Branch:** `chicago-v2`
66+77+## Summary
88+99+An alternative entry experience for SkyPress where **writing is the first thing you do**.
1010+Today the flow is login-first: `/` is a marketing landing page and the editor at `/editor`
1111+gates the entire writing surface behind `status === 'signed-in'`. This design inverts that:
1212+the writer lands directly on an editor, starts writing immediately, and only encounters
1313+auth and publication selection at publish time.
1414+1515+This ships as a **parallel experience** at a new route — `/`, `/editor`, and `/dashboard`
1616+are left untouched so the two funnels can be compared side by side.
1717+1818+## Goals
1919+2020+- A writer can land on a page and start writing with zero friction — no login gate.
2121+- Authentication is deferred to publish time and resumes the publish flow automatically
2222+ after the OAuth redirect round-trip.
2323+- Image insertion works while signed out; uploads are deferred to publish.
2424+- Publishing branches sensibly on how many publications the writer owns (one / many / zero).
2525+- A returning, already-signed-in writer gets a small account pill but the editor stays the
2626+ focus.
2727+2828+## Non-goals
2929+3030+- **Not** a replacement for the current landing page or `/editor`. This is option B
3131+ (parallel route), explicitly for comparison.
3232+- **No remote account creation.** atproto OAuth is a sign-in protocol; a third-party client
3333+ cannot create accounts on a big PDS, and SkyPress must never become a PDS/signup broker
3434+ (product guardrail). We surface only a lightweight "Need an account? →" link to Bluesky's
3535+ hosted signup.
3636+- **New-document only.** This is a "start writing" funnel. Editing an existing article stays
3737+ on `/editor` (`Studio`). No `?edit=<rkey>` load path here.
3838+- **No inline publication management.** Creating/editing publications in general stays on the
3939+ existing `/dashboard`. The one exception is a focused inline "create your first publication"
4040+ step inside the zero-publication publish branch.
4141+4242+## Decisions (from brainstorming)
4343+4444+| # | Decision |
4545+|---|----------|
4646+| Q1 | Positioning: **parallel route** (`/write`), existing routes untouched. |
4747+| Q2 | Account creation: **sign-in only** for now, with a "Need an account? →" link to Bluesky signup. No real create-account branch. |
4848+| Q3 | Images signed-out: **insert freely, upload silently at publish** (invisible deferral). |
4949+| Q4a | Single-publication publish: **always show a lightweight confirm** ("Publish to *Name* — also posts to Bluesky"), never silent auto-publish. |
5050+| Q4b | Zero-publication publish: **inline create-publication step** (reuse `PublicationForm`), not a redirect to `/dashboard`. |
5151+| Q5 | Signed-in pill: **links out to existing `/dashboard`** for management; pill itself is the only new account surface. |
5252+| Q6 | Media: **always defer (one path)** — hold locally and upload at publish regardless of auth state. |
5353+5454+## User journey
5555+5656+### Signed-out writer
5757+1. Lands on `/write` → clean editor (title, lede, blocks, cover). No gate.
5858+2. Writes; inserts images (held locally, previewed inline).
5959+3. Clicks **Publish** → draft + images persisted locally, `publishIntent` set → OAuth redirect.
6060+4. Returns signed in → draft restored, `publishIntent` detected → publish flow auto-resumes,
6161+ branching on publication count (see below).
6262+5. Success → draft cleared, published pill/link shown.
6363+6464+### Already-signed-in writer
6565+1. Lands on `/write` → same editor, plus an **account pill** (avatar + handle) top-right.
6666+2. Clicks **Publish** → no redirect; publish flow runs in place, same branching.
6767+6868+### Publish branching (after auth is guaranteed)
6969+- **Exactly one publication** → "Publish to *Name* (also posts to Bluesky)" confirm →
7070+ upload held images → commit document + Bluesky post.
7171+- **More than one** → publication picker step → confirm → publish.
7272+- **Zero** → inline "create your first publication" step (`PublicationForm`) → publish.
7373+7474+## Architecture
7575+7676+### Route & island
7777+- New page `src/pages/write.astro` mounting a client-only island **`WriteStudio`**.
7878+- `WriteStudio` wraps `AuthProvider` (reused) but, unlike `Studio`, **renders the editor for
7979+ every auth status** (`loading` / `signed-out` / `signed-in`). There is no login gate.
8080+- The OAuth `redirect_uri` is the current pathname (existing `oauth.ts` behavior), so a
8181+ sign-in initiated from `/write` returns to `/write` automatically. No new redirect config.
8282+8383+### Top-right corner states
8484+- **Signed out:** a "Sign in" affordance + a small "Need an account? →" link to Bluesky
8585+ signup. Reuses the existing handle-input / `signIn()` path from `AuthProvider`.
8686+- **Signed in:** the **account pill** — avatar + handle opening a menu: *Manage publications*
8787+ (→ `/dashboard`), *Profile*, *Sign out*.
8888+8989+### Draft persistence (survives the OAuth redirect)
9090+Sign-in is a full-page redirect that destroys in-memory state, so before any redirect we
9191+persist a **single draft slot**:
9292+- **localStorage** — title, lede, serialized blocks, cover ref/preview, and a `publishIntent`
9393+ flag.
9494+- **IndexedDB** — held image bytes (data URLs can exceed localStorage quota), keyed so blocks
9595+ can reference them on restore.
9696+9797+On `/write` load we **auto-restore** the draft if present. After a successful publish we
9898+**clear** both stores. Abandoned drafts persist for the next visit.
9999+100100+### Deferred media (one path)
101101+A new **local-hold media handler** replaces the eager PDS-upload handler for this flow, used
102102+regardless of auth state:
103103+- On insert: store image bytes in IndexedDB, render an inline preview (data URL), and record
104104+ the mapping (preview → held key) in the in-memory blob registry.
105105+- At publish: for every held image, `agent.uploadBlob()` → real `BlobRef`, then reuse the
106106+ existing `attachBlobRefs()` registry mechanism to swap preview URLs for blob refs before the
107107+ document record commits.
108108+- Errors surface at publish (acceptable trade-off vs. eager-upload early feedback).
109109+110110+### Publish-flow stepper
111111+A small state machine driven by `publishIntent` + publication count:
112112+- `idle` → (Publish clicked) → persist + maybe-redirect → `resolving-auth`
113113+- `resolving-auth` → (signed in) → `branch`
114114+- `branch` → one of `confirm-single` / `pick` / `create-pub`
115115+- any terminal step → upload images → `publish()` (document + post) → `done` (clear draft).
116116+117117+Reuses `publisher.ts` (`publish`), `publications.ts` (`listPublications`, `createPublication`),
118118+`PublicationForm`, and the published-pill UI.
119119+120120+## Reuse vs. build
121121+122122+**Reuse (unchanged):** `AuthProvider`, `oauth.ts`, `SkyEditor`, `PublicationForm`,
123123+`publisher.ts` (`publish`), `publications.ts`, `attachBlobRefs`, published-pill UI.
124124+125125+**Build new:**
126126+- `src/pages/write.astro` route.
127127+- `WriteStudio` island (editor for all auth states; corner state; publish stepper host).
128128+- Local-hold media handler.
129129+- Draft-persistence module (localStorage + IndexedDB; save / restore / clear).
130130+- Account pill / signed-out corner component.
131131+- Publish-flow stepper component (confirm / pick / create-pub) driven by `publishIntent`.
132132+133133+**Untouched:** `/` (`index.astro`), `/editor` (`Studio`), `/dashboard`.
134134+135135+## Testing
136136+137137+- Colocated page tests under `src/pages/` MUST be underscore-prefixed (e.g.
138138+ `_write.meta.test.ts`) per the Astro file-router constraint.
139139+- Draft persistence: save → simulate reload → restore yields identical editor state
140140+ (title/lede/blocks/cover); clear empties both stores.
141141+- Deferred media: held images survive a persist/restore round-trip; at publish each held
142142+ image is uploaded once and its preview URL is swapped for the blob ref before commit.
143143+- Publish branching: one / many / zero publications each route to the correct step; the
144144+ single-publication case still shows a confirm (never silent).
145145+- Resume-after-redirect: a `publishIntent` present on load auto-resumes the publish flow
146146+ rather than dropping into the editor.
147147+- `WriteStudio` renders the editor for `loading`, `signed-out`, and `signed-in` (no gate).
148148+149149+## Open questions
150150+151151+None outstanding. Durable rationale (e.g. the deferred-media single-path choice and the
152152+no-remote-account-creation constraint) should graduate to a `docs/decisions/NNNN-*.md` during
153153+implementation.