Grid layout transitions — v13 sketch (feedback ballot-box auth, precise gate) (Firewatch Camera Grid)

**Note** from Bead: Firewatch Camera Grid · [canonical source](https://redfish.acequia.io/guerin/.agents/65783732-7907-4a36-983a-3b015e75e80b/2026-06-24/notes/07-grid-layout-v13-sketch.md) · session 2026-06-24 · discussion: Talk: Firewatch Camera Grid

**Rendered:** https://redfish.acequia.io/guerin/.agents/65783732-7907-4a36-983a-3b015e75e80b/2026-06-24/artifacts/sketches/grid-layout-transitions-v13.html v13 is a surgical change on [v12](./06-grid-layout-v12-sketch.md): it makes the write-credential decision match Stephen's rule exactly and finishes the anonymous-voting story.

## Stephen's rule (verbatim) > "to be clear, you only use that [the ballot-box token] for anonymous users that don't have a token > in indexeddb."

## How auth now works (`writeVote`, layered, gated) 1. **Token-holder → their OWN identity.** If the visitor has a usable acequia identity (`acequia.getUserId()` after `acequiaReady()`), the vote is written with their own pre-authenticated client (`acequia.webdav.createAcequiaClient().putFileContents`). Owner/granted ⇒ 201. 2. **Anonymous → the ballot-box token.** Only when the visitor has **no usable token** — either no acequia identity at all, or an identity the server **rejects for writing here (401/403)**, which is the incognito anon-keypair case — does the page fall back to the embedded **ballot-box token** (a write-only key scoped to `feedback/`). Silent, no modal. 3. **Never spend the ballot key on a real token-holder's transient error.** A non-auth failure (network/5xx) for a token-holder is surfaced as an error and retried — it does **not** fall through to the ballot key (the v12 behaviour that this tightens). 4. **Paste-token modal = last resort**, only if the ballot write itself is rejected (should never happen). The vote JSON records only the visitor's `name`/`userId`/`deviceId`/`tokenId` (a `tok:` fingerprint) for attribution — **never a raw user token**. The ballot-box token is the one intentionally-embedded write credential.

## Why a ballot-box token was needed (the diagnosis) acequia.js gives an incognito visitor an identity (deviceId/userId/keypair in IndexedDB) but **not write scope** to the `guerin/` namespace, so their own write to `feedback/` returns **401** (proven in a fresh incognito context: identity `clear-dune`/`jade-heron` read fine, write 401). A sidecar can grant anonymous **read** but never **write** (platform: `public-access.md`, resolved Q5). So anonymous writes require an owner-delegated capability token.

## The ballot-box token - Minted server-side via **`POST /auth/create-token`** (the [Token Management API](https://acequia.io/documentation/platform/token-management-api.md)) with the owner bearer — **server-attested** (signed by `server-redfish.acequia.io`), no client key needed. - Scope: `{ paths:[…/feedback/*], writePaths:[…/feedback/*] }` — **write-only to `feedback/`**, depth 1, 180-day expiry. Verified: PUT into `feedback/` → 201; PUT anywhere else → **403**; anon read → 200. - **tokenId `hxhs9al8o865ljoj3ijet4d9`.** Revoke any time: `DELETE /auth/tokens/hxhs9al8o865ljoj3ijet4d9` with the owner bearer (cascades to nothing else). Stored at `.credentials/feedback-ballot-token.txt`, embedded in the page as `BALLOT_TOKEN`.

## Directory The ballot box is **`feedback/`** (renamed from v11's `votes/`), `.acequia-access.json` `{read:anonymous,recursive:true}` so every teammate's dashboard reads it without auth. One JSON file per voter (`grid-layout-v13__<voterKey>.json`, last-write-wins); the dashboard tallies any `grid-layout*` so earlier votes still show.

## Kept from v8–v12 Single-scene `lerp(mapPos,gridPos,t)` morph (zero create/destroy), position-preserving placement (NAIVE↔POSITION, crossings→0), dim-ghost basemap, MAP◀▶GRID scrubber, viewport-implicit selection + live filtering, 2D/3D drag-pan, per-card un-pin/re-flow, OrientedMap glyphs + tie-point ray, 3D oblique, TIME cursor, identity color, the 5s-polling results dashboard (the processor — no drainer), onboarding overlay + Buxton link, "Redfish Team" label. 180° axis stays removed.

## Verified - Ballot-box token: write `feedback/` 201, write-elsewhere 403, anon read 200 (curl). - v13 inline script: `node --check` PASS. - Live: page serves as v13, acequia.js loads + SW registers + anon identity derived (verified on v10/v11). - *Pending:* live incognito **UI** end-to-end (pick fork → send → vote lands silently) — the dedicated CDP browser was closed mid-session; re-run when available, or confirm in a real incognito window.