Skip to content

PRD: Feature roadmap — project library, cloud sync, accounts, subscription #72

Description

@Flow-Fly

PRD — Feature Roadmap

Date: 2026-07-06 (rev. 2 — added owner feedback: multi-open directions, palette rework, reference layers, CRT effect, paint-by-number)
Status: Draft

Live roadmap state: canonical current-state comment. Conductors should edit that comment in place; child issues and PRs hold the historical evidence.
Source: codebase audit (CODEBASE_AUDIT.md, §5–6) + owner feedback

Purpose

This is a meta-PRD: a prioritized roadmap where each feature section is written to be lifted into its own full PRD (or broken directly into issues with $to-issues). Long-term product goal: a backend that stores and syncs pixel-art drawings, with accounts and a low monthly subscription for more sync.

Guiding constraints

Suggested order of operations

Hardening    : #67 CI → serialization+tests (#68/#69/#71) → #63 store cycle
Core rework  : F11 palette rework (BEFORE the backend freezes the schema)
Product      : F1 project library (→ F1b multi-open tabs) → F12 reference layers → F8 PWA
Fun/growth   : F13 CRT effect → F14 paint-by-number (pairs with F5 share links)
Backend      : F2 accounts + sync MVP → F3 billing → F4 asset sync
Growth/paid  : F5 share links → F6 delta sync + version history → F7 collab

F1 — Local project library (multi-project)

Problem: the app persists exactly one project ('current-project' IndexedDB slot). Users cannot keep multiple works in progress.
Solution: new/open/duplicate/rename/delete with thumbnails, stored in the existing IndexedDB sprites store keyed by project UUID. Entry point: File menu + a start screen / project browser.
Why first: forces the project-identity model and repository interface (#65) that cloud sync requires; big UX win on its own.
Scope notes: thumbnail = downscaled composite of frame 1; store lastOpenedProjectId in settings; migrate the legacy single slot into a normal project record on first run.
Acceptance sketch: create ≥2 projects, switch between them with full undo isolation, reload restores the last-opened one, deleting prompts for confirmation.
Depends on: #65.

F1b — Multiple projects open at once

Problem: switching projects (F1) still means one project at a time; artists want to work across pieces (compare, copy pixels/palettes).
Candidate directions:

  • Tabs (recommended first): one active project, editor chrome rebinds to it. Simplest mental model, cheapest rendering (one live canvas), and covers most of the value.
  • Split view (second step): two tabs visible side by side; only one has input focus, panels follow focus. Main payoff: copy/paste and palette reuse across projects.
  • Free-floating windows in workspace: most flexible, most expensive (N live viewports, window management, per-window rendering). Recommend deferring indefinitely unless split view proves insufficient.

The real work is not the UI — it is state. Every store is currently a module singleton; "which project is selected" must become a first-class concept:

  1. Extract a ProjectContext (layers, animation, palette, selection, history, viewport, dirty-rect per project) — this is Architecture: break the singleton store web (history ↔ project circular import) #63's composition root taken to its conclusion.
  2. An activeProjectContext signal; all panels (palette panel notably, timeline, layers, context bar) read through it and rebind on switch.
  3. Per-project undo stacks and auto-save (history context machinery already exists and helps).
  4. Cross-project clipboard is the killer feature of multi-open — design it explicitly (palette-index remap on paste between projects with different palettes).

Acceptance sketch (tabs): two projects open in tabs; switching tabs swaps canvas, palette, timeline, layers, and undo history correctly; copying a selection from tab A pastes into tab B with sensible color handling.
Depends on: F1, #63. Sizing: large — plan as its own PRD with the ProjectContext refactor as slice 1.

F2 — Accounts + cloud sync MVP (the backend)

Problem: work lives in one browser's IndexedDB; clearing site data destroys it; no cross-device access.
Solution: email magic-link or OAuth login; sync the project library as whole-project blobs, last-write-wins per project; conflict = "keep both copies". Client stays local-first — sync is a background reconciler behind the ProjectRepository interface.
Suggested stack (boring on purpose): Postgres (accounts, project metadata, entitlements) + S3-compatible object storage for project blobs (already on Scaleway) + a thin API (Node/Hono/Fastify or a BaaS like Supabase/PocketBase to start). Store ProjectFile as the canonical schema — which is why #71/#69 and F11 come first.
Acceptance sketch: sign in on two browsers, edit on A, see it on B after sync; kill network mid-edit, keep working, reconcile on reconnect.
Depends on: F1, F11, #71, #69, #68.

F3 — Subscription billing (Stripe)

Problem: monetization for sync costs.
Solution: Stripe Checkout + customer portal; webhooks update an entitlements table checked by the sync API. Free tier: e.g. 3 synced projects; paid: unlimited projects + version history (F6) + future collab (F7). Local-only usage stays free and unlimited forever.
Acceptance sketch: upgrade unlocks limits within a minute of webhook; cancel downgrades gracefully (data readable, sync of over-quota projects paused, never deleted).
Depends on: F2.

F4 — Cross-device asset sync (palettes & brushes)

Problem: custom palettes/brushes are stuck per device.
Solution: sync the small payloads first — palette-persistence.ts / brush-persistence.ts already isolate the data. Ideal low-risk shakedown of the sync engine before/alongside broad project sync.
Depends on: F2 (auth), can ship before full project sync is battle-tested.

F5 — Shareable read-only links

Problem: no way to show work without exporting and uploading elsewhere.
Solution: publish a sprite/animation to a public URL — server renders/stores GIF/WebP/PNG plus a lightweight embedded viewer page. Growth loop; also the first "public" backend surface with minimal abuse area (read-only).
Acceptance sketch: "Share" produces a URL that renders the animation without an account; unpublish works.
Depends on: F2. Synergy: shared pages could offer the CRT effect (F13) as a viewer toggle, and paint-by-number boards (F14) are inherently shareable.

F6 — Delta sync + cloud version history (paid-tier feature)

Problem: whole-project blob sync re-uploads everything per change and offers no history.
Solution: content-addressed per-cel blobs (hash the index buffers / PNG bytes), a project.json manifest referencing hashes, keep the last N manifests per project = version history nearly for free. The dirty-rect + patch-command work (#66) provides the change granularity.
Acceptance sketch: editing one cel uploads only that cel; user can browse and restore previous cloud versions (paid tier).
Depends on: F2, #66.

F7 — Real-time collaboration (later, paid tier)

Problem/opportunity: the command pattern already stamps userId on every command and supports per-user undo — it was built for this.
Solution: server-authoritative command log per project session; replay commands to followers; CRDTs only if free-form concurrent editing demands it. Do not start before F6 is stable.
Depends on: F2, F6, #63.

F8 — PWA / installability

Problem: the app feels like a website; offline behavior is incidental.
Solution: service worker with offline caching of the shell, install prompt, file-handler registration for .aseprite/.pf so OS double-click opens the app. Reinforces the local-first story; independent of the backend.
Depends on: nothing — can ship anytime.

F9 — Serialization overhaul (tracked in #71/#69/#68)

Listed for roadmap completeness: versioned schema + migration pipeline + round-trip tests is the gate between "product features" and "backend features". Treat #71 as its PRD. Coordinate with F11 — the palette model decision changes the schema; make both changes in the same format-version bump (v4) rather than two.

F10 — CI & guardrails (tracked in #67)

Same: listed for completeness; treat #67 as the actionable issue.

F11 — Palette rework: one palette, one truth (core-tool hardening)

Problem: the palette is the core tool but currently has a dual model: a main palette plus an "ephemeral/untracked" stack for colors drawn that aren't in the palette, with promotion ("track this color"), demotion, index remapping, rebuild-from-drawing, and a v3.1 file field. The machinery spans 12 files (stores/palette/*, animation/palette-sync.ts, promotion events, rebuildEphemeralFromDrawing, clearEphemeralColors(skipRemap)), and the owner's own assessment is that it may solve a problem that doesn't exist. Two color lifecycles = twice the states a user (and the code) must reason about.

Direction — collapse to a single lifecycle:

  • The palette is the single source of truth. Drawing with a color not in the palette simply adds it to the palette, at most visually flagged "new/unsorted" (one boolean badge, not a parallel collection with its own indices).
  • Deleting a palette color keeps explicit, predictable semantics (offer: replace usages with nearest color / with transparency — the remap machinery that exists today is reusable here).
  • Loading a file always loads that file's palette (already true for v3.0+ imports via loadProject; make the auto-save path and legacy formats behave identically so there is exactly one rule).
  • Palette presets/switching stay as-is (they already work well per the owner).

Payoff: kills the most complex duplicated state in the app (see #71 item 6), simplifies ProjectFile (drop ephemeralPalette in v4 with a migration folding old ephemeral colors into the palette), and gives sync a simpler canonical schema.
Acceptance sketch: draw with a non-palette color → it appears in the palette flagged "new"; save/load round-trips exactly one color list; old v3.1 files load with ephemeral colors folded in; no user-visible "promote/track" concept remains (or it becomes just "clear new flag").
Depends on: #68 (tests first), coordinates with #71/#69 (same format bump). Do before F2 freezes the schema.

F12 — Reference image layers (background guides)

Problem: artists work from references; today the only way is a second window or importing the image as pixels into a drawing layer (destructive, wrong resolution).
Solution: a new reference layer type — the layer system already discriminates types (image | text), so it extends cleanly:

  • Stores the original bitmap untouched (PNG/JPEG bytes in the project file), rendered with its own transform: position, scale (non-integer allowed), opacity; move/resize via the existing pf-transform-handles interaction.
  • Never contributes to export, palette, index buffers, or flattening; excluded from magic wand/fill sampling; lockable and hideable like any layer.
  • Nice-to-haves: opacity slider default ~50%, desaturate toggle, "above/below artwork" position.
    Acceptance sketch: import an image as reference, move/scale it freely at any zoom, draw over it, export contains zero reference pixels, project save/load restores the reference with its transform.
    Depends on: schema addition — coordinate with the v4 format bump (F11/Refactor: complexity hotspots — suggested decompositions #71) to avoid a v4.1 right behind it.

F13 — CRT display effect (non-destructive)

Problem/opportunity: pixel art was born on CRTs — scanlines, phosphor masks, and color bleed are part of the aesthetic. A live CRT view makes work look great and is pure delight.
Solution: a display-only effect pipeline (never touches pixel data, cels, or the project file — same principle as the preview background modes in #52):

  • WebGL/WebGPU shader pass over the composited output: scanlines, phosphor/aperture-grille mask, barrel curvature, bloom/bleed, vignette — each with intensity sliders + a few presets.
  • Apply in the live preview overlay first (cheapest surface), then optionally the main viewport ("view mode: CRT") and export (baked into GIF/WebP/PNG copies at export time only, clearly labeled).
  • Architecture note: build it as a generic post-process view-effect slot, not a hardcoded CRT toggle — future effects (LCD grid, Game Boy palette preview) reuse the slot.
    Acceptance sketch: toggle CRT on the preview, tune intensity, project data unchanged (verify via save/load hash); export "with effect" produces a separate styled copy.
    Depends on: nothing hard; keep it out of ProjectFile (settings-level preference).

F14 — Paint-by-number: gamified pixel-art creation

Problem/opportunity: onboarding + fun. Turn any image into a guided pixel-art exercise: generate a pixel-art rendition and palette from a photo, but show a numbered grid instead of the finished art, so the user paints it cell by cell.
Solution sketch (pipeline):

  1. Image upload → downscale to target sprite size → color quantization to N colors. Building blocks already exist: stores/palette/extraction.ts (palette extraction) and the indexed-color model is literally numbers-per-pixel (the cel index buffer is the paint-by-number key).
  2. Generate a "guided project": palette = quantized colors (numbered swatches), each cell renders its target number until painted; painting with the correct color fills the cell, wrong color gives soft feedback.
  3. Completion state (per-color progress, % done), then reveal the finished piece — optionally with the CRT effect (F13) for the wow moment.
    Product notes: works fully client-side (no backend needed for MVP); user-uploaded images stay local. Later synergies: share a puzzle link (F5), daily challenge (backend), premium puzzle packs (F3).
    Acceptance sketch: upload a photo, pick sprite size + color count, get a playable numbered board; finishing reveals the artwork; the result is a normal project afterwards (fully editable).
    Depends on: palette rework (F11) strongly recommended first — guided mode leans entirely on a trustworthy single-palette model. Sizing: large — own PRD when picked up.

Out of scope (for this roadmap)

Issue Breakdown

Implementation slices are intentionally not created here. Lift one feature section (F1–F14) at a time into a full PRD, or use $to-issues on a feature section to create child issues and link them as sub-issues. Recommended first picks: F11 (palette rework) together with #71's serialization work, then F1 (project library) after #65/#67 land.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions