You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Design and build a companion app to dewpt: a mobile-first, Reigns-like card stack where the user swipes a card in one of four directions, and each direction is a move along a named semantic axis. The swipe changes the user's position in latent space; the next card is generated for where they now stand. After this ships, there is a second, structurally different UI over dewpt's existing latent-space engine — which is also the test of whether that engine is genuinely renderer-agnostic or quietly welded to the ambient field.
This issue is parked, not queued. It captures the idea and the reasoning while they are fresh; nothing here is committed to a milestone.
Context
Came out of a companion-UI brainstorm on 2026-08-08 that produced three candidates: this one, a kanban board with LLM-authored columns (being specced separately, in progress), and a three.js maze with graffitied walls (not pursued — most of the build is 3D plumbing that teaches nothing about latent space).
The brainstorm's load-bearing observation: all three ideas are the same engine with a different navigation gesture. dewpt already owns the hard parts — the never-block-on-AI candidate pool, banded generation, pole expansion, axis projection, and the per-session DO. What differs between UIs is only the renderer and the gesture.
Two prior results make this idea cheap rather than speculative:
docs/latent-space-navigation-design.md established "position is the query" — where you stand in axis-space is what gets generated. A swipe is exactly a slider move, discretized. This mechanic is already designed; this issue reuses it rather than inventing one.
The axis spike validated pole expansion empirically: descriptive phrases score AUC 0.980 against 0.640 for bare terms. Reproducible via npm run axis-phrasing-spike.
SPEC.md:81 made mobile-first an explicit non-goal for dewpt ("desktop pointer interaction is the core"). This companion covers that ground instead of forcing dewpt to.
Motivation
User story: As someone brainstorming on a phone, I want to steer an idea stream with my thumb, so that setting direction is one gesture instead of three sliders and a pointer.
What triggered the ask now: the companion-UI exploration above, plus the recognition that dewpt's engine has never been exercised by a second consumer. A second UI is the only honest test of the abstraction.
The mechanic — decided enough to start, not decided enough to build
Four swipe directions map to semantic directions. Two candidate schemes, and picking between them is the first real task:
(a) Two bidirectional axes. Swipe left/right = −/+ along axis A; up/down = −/+ along axis B. Position is a 2D point. Reversible: you can swipe back to where you were. Closest to the existing slider model and to map mode (#29).
(b) Four independent directions (Reigns' four realms). Each swipe adds that pole's vector to your position, and four meters show how far you have drifted along each. Not reversible — drift accumulates. Closer to the actual Reigns feel, and gives the session a shape (a trajectory) rather than a location.
They are different games and the choice is not cosmetic. (a) makes the app a navigator; (b) makes it a journey with consequences.
The fail state is worth keeping from Reigns. In Reigns you lose when a meter bottoms out or maxes. The analogue here is real: push far enough along one axis and generation goes incoherent. That is a truthful thing to teach about latent space — the extremes genuinely are noise — and it gives the loop stakes rather than being an endless feed. Worth a spike to find where the incoherence threshold actually sits, rather than assuming one.
Open, deliberately: what is on a card? One word, a phrase, or a small cluster? Reigns shows one prompt plus two visible consequences; the consequence-preview has no obvious analogue here and may not need one.
Pointers
Substrate to reuse:
src/pool-core.ts:77 — PoolCore, pure and heavily tested; the never-block-on-AI pool. Likely reusable, but the bucket scheme at src/types.ts:8 (BUCKET_KEYS, tier × alt) is dewpt-specific and probably needs regeneralizing for axis-position buckets. Expect this to be the main friction point.
src/generation.ts:295 — expandPole. Mandatory, not optional; rationale at src/types.ts:87.
src/axis-core.ts:11 — axisVector; :22coordsFor; :28normalizeCoords. The projection math, ready to use.
src/session-do.ts — the thin stateful shell pattern. Keep logic out of it, per CLAUDE.md.
src/index.ts:60 — handleApi, the API shape to mirror.
src/dev-fake-ai.ts — deterministic pseudo-embeddings; makes swipe/position logic testable with no network.
public/pool-client.js:17 — createPoolClient, the client-side drip buffer and the axis-id binding it carries.
Design context to read before writing anything:
docs/latent-space-navigation-design.md — "position is the query" (§Core mechanic), the axis spike evidence table, and the CAP = 14 legibility finding.
docs/press.md — the Press design language. Decide explicitly whether the companion inherits it or gets its own; do not drift into a half-inherited look.
Constraints
Cloudflare Workers + Durable Objects + Workers AI only. No external API keys (SPEC.md:40).
A swipe must resolve instantly. Never block a card on an AI call — pool depth is a correctness requirement, not an optimization (CLAUDE.md §Design constraints).
Pole expansion runs before any user-typed term is embedded. A degraded (unexpanded) pole must stay visibly degraded.
No embeddings on the wire (docs/latent-space-navigation-design.md §Consequence: the wire format stays small).
Mobile-first for real: no tap target under 44 pt, dvh not vh, viewport-fit=cover + safe-area insets, no horizontal scroll at 390 px.
prefers-reduced-motion degrades the card fling to a cross-fade.
Core logic lives outside the DO and carries vitest coverage, per CLAUDE.md §Gates.
Decide the repo question up front and write it down: separate repo, or a second entrypoint in this one. Either is defensible; drifting is not.
Acceptance criteria
A design doc in docs/superpowers/specs/ picks one of the two direction-mapping schemes and states the basis for the choice.
A swipe resolves to the next card with no visible wait, verified both against dev-fake-ai and against live Workers AI.
Swiping consistently in one direction produces monotonic drift in that axis's projection across ≥10 cards — the navigation demonstrably navigates. Report the number, in the style of the existing spike scripts.
Usable one-thumbed at ≤390 px viewport width: every target ≥44 pt, no horizontal scroll, safe-area respected.
prefers-reduced-motion replaces the fling with a cross-fade, with no drift animation.
npm run typecheck and npm test pass, with counts reported.
Out of scope
The kanban companion (specced separately) and the 3D maze idea.
Accounts, auth, or cross-session persistence. The session URL is the session, as in dewpt (SPEC.md:80).
Multiplayer.
Extracting a shared engine library. Build against dewpt's modules directly. The second consumer is what reveals the real seam; record the friction, do not refactor speculatively.
Any change to dewpt's own field UI.
Reasoning guidance
The direction-mapping choice is the crux and is harder than it looks — the two schemes produce different games, and the decision propagates into the pool's bucket scheme, the API, and the fail state. Think carefully and step-by-step before committing to one, and prefer measurement (in the style of scripts/axis-layout-prototype.ts) over intuition.
Task
Design and build a companion app to dewpt: a mobile-first, Reigns-like card stack where the user swipes a card in one of four directions, and each direction is a move along a named semantic axis. The swipe changes the user's position in latent space; the next card is generated for where they now stand. After this ships, there is a second, structurally different UI over dewpt's existing latent-space engine — which is also the test of whether that engine is genuinely renderer-agnostic or quietly welded to the ambient field.
This issue is parked, not queued. It captures the idea and the reasoning while they are fresh; nothing here is committed to a milestone.
Context
Came out of a companion-UI brainstorm on 2026-08-08 that produced three candidates: this one, a kanban board with LLM-authored columns (being specced separately, in progress), and a three.js maze with graffitied walls (not pursued — most of the build is 3D plumbing that teaches nothing about latent space).
The brainstorm's load-bearing observation: all three ideas are the same engine with a different navigation gesture. dewpt already owns the hard parts — the never-block-on-AI candidate pool, banded generation, pole expansion, axis projection, and the per-session DO. What differs between UIs is only the renderer and the gesture.
Two prior results make this idea cheap rather than speculative:
docs/latent-space-navigation-design.mdestablished "position is the query" — where you stand in axis-space is what gets generated. A swipe is exactly a slider move, discretized. This mechanic is already designed; this issue reuses it rather than inventing one.npm run axis-phrasing-spike.SPEC.md:81made mobile-first an explicit non-goal for dewpt ("desktop pointer interaction is the core"). This companion covers that ground instead of forcing dewpt to.Motivation
User story: As someone brainstorming on a phone, I want to steer an idea stream with my thumb, so that setting direction is one gesture instead of three sliders and a pointer.
What triggered the ask now: the companion-UI exploration above, plus the recognition that dewpt's engine has never been exercised by a second consumer. A second UI is the only honest test of the abstraction.
The mechanic — decided enough to start, not decided enough to build
Four swipe directions map to semantic directions. Two candidate schemes, and picking between them is the first real task:
(a) Two bidirectional axes. Swipe left/right = −/+ along axis A; up/down = −/+ along axis B. Position is a 2D point. Reversible: you can swipe back to where you were. Closest to the existing slider model and to map mode (#29).
(b) Four independent directions (Reigns' four realms). Each swipe adds that pole's vector to your position, and four meters show how far you have drifted along each. Not reversible — drift accumulates. Closer to the actual Reigns feel, and gives the session a shape (a trajectory) rather than a location.
They are different games and the choice is not cosmetic. (a) makes the app a navigator; (b) makes it a journey with consequences.
The fail state is worth keeping from Reigns. In Reigns you lose when a meter bottoms out or maxes. The analogue here is real: push far enough along one axis and generation goes incoherent. That is a truthful thing to teach about latent space — the extremes genuinely are noise — and it gives the loop stakes rather than being an endless feed. Worth a spike to find where the incoherence threshold actually sits, rather than assuming one.
Open, deliberately: what is on a card? One word, a phrase, or a small cluster? Reigns shows one prompt plus two visible consequences; the consequence-preview has no obvious analogue here and may not need one.
Pointers
Substrate to reuse:
src/pool-core.ts:77—PoolCore, pure and heavily tested; the never-block-on-AI pool. Likely reusable, but the bucket scheme atsrc/types.ts:8(BUCKET_KEYS, tier × alt) is dewpt-specific and probably needs regeneralizing for axis-position buckets. Expect this to be the main friction point.src/generation.ts:295—expandPole. Mandatory, not optional; rationale atsrc/types.ts:87.src/axis-core.ts:11—axisVector;:22coordsFor;:28normalizeCoords. The projection math, ready to use.src/session-do.ts— the thin stateful shell pattern. Keep logic out of it, perCLAUDE.md.src/index.ts:60—handleApi, the API shape to mirror.src/dev-fake-ai.ts— deterministic pseudo-embeddings; makes swipe/position logic testable with no network.public/pool-client.js:17—createPoolClient, the client-side drip buffer and the axis-id binding it carries.Design context to read before writing anything:
docs/latent-space-navigation-design.md— "position is the query" (§Core mechanic), the axis spike evidence table, and theCAP = 14legibility finding.docs/mobile-research.md— the touch gap inventory (tap targets, hover-only legibility,dvh, safe-area, spawn clamp). Shipped for dewpt via Make dewpt usable on mobile as an installable PWA #17 / PR Make dewpt usable on mobile as an installable PWA (#17) #24; the findings are this app's mobile baseline and should not be rediscovered.docs/press.md— the Press design language. Decide explicitly whether the companion inherits it or gets its own; do not drift into a half-inherited look.Constraints
SPEC.md:40).CLAUDE.md§Design constraints).docs/latent-space-navigation-design.md§Consequence: the wire format stays small).dvhnotvh,viewport-fit=cover+ safe-area insets, no horizontal scroll at 390 px.prefers-reduced-motiondegrades the card fling to a cross-fade.CLAUDE.md§Gates.Acceptance criteria
docs/superpowers/specs/picks one of the two direction-mapping schemes and states the basis for the choice.dev-fake-aiand against live Workers AI.prefers-reduced-motionreplaces the fling with a cross-fade, with no drift animation.npm run typecheckandnpm testpass, with counts reported.Out of scope
SPEC.md:80).Reasoning guidance
The direction-mapping choice is the crux and is harder than it looks — the two schemes produce different games, and the decision propagates into the pool's bucket scheme, the API, and the fail state. Think carefully and step-by-step before committing to one, and prefer measurement (in the style of
scripts/axis-layout-prototype.ts) over intuition.Cross-references