Skip to content

Surfc — User Journey v2 Design Spec

Surfc — User Journey v2 Design Spec

Date: 2026-04-09
Status: Approved for implementation
Scope: Mobile-only UI/UX overhaul. Desktop layout untouched.
Approach: Two-branch delivery — structure first, animations second.


1. Design Goals

This overhaul applies a Capture-First architecture to reduce cognitive load and friction across the core user journey. The warm “paper/book” aesthetic defined in DESIGN.md and tokens.css is preserved throughout. No new dependencies are introduced.

The three friction points being resolved:

  1. Home Screen was a dead end — now a live dashboard with navigable stat boxes.
  2. ReviewScreen created an unnecessary “Continue” hurdle — removed entirely.
  3. New Note Screen had visual noise (Library grid) and manual AI triggering — both eliminated.

2. Implementation Approach

Branch 1 design/ui-structure-overhaul
All navigation, new screens, HomeScreen redesign, NoteForm cleanup, ReviewScreen removal, auto-discover chaining, FUNCTIONAL.md updates. Fully functional without animations.

Branch 2 design/capture-animations
Card slide animation, ink bleed text animation, idea chip fade-in. Pure CSS — layered on top of the working structure from Branch 1.


3. Navigation Architecture

Default launch state

useUI.js initial mobileView changes from 'home' to 'capture'. The app opens directly on the camera viewfinder on every launch.

View states

StateScreenNotes
captureCamera viewfinderDefault on launch
homeStat box dashboardAccessible from bottom nav
indexNotes listAccessible from bottom nav
noteNew note formVia shutter tap or Write Manually
sourcesSources managerVia Home stat box — NEW
active-ideasActive ideas listVia Home stat box — NEW
related-notesRelated notesVia note long-press — unchanged
reviewTranscription reviewREMOVED

Bottom navigation contract

Three buttons — Home / Capture / Index — present on every screen except Home.

The Home Screen is the only exception: it renders a single full-width “Start Capturing” CTA button at the bottom instead of the nav bar.

Active state: the button corresponding to the current view is highlighted with --color-accent. When on sources or active-ideas, the Home button is highlighted (they are children of Home).

Settings

A <Gear weight="light" size={22} /> (Phosphor React) icon sits in the top-right corner of every screen except the New Note screen. Tapping it opens the existing SettingsModal.

Global header removal

The current App.jsx renders a global <header className="header"> containing the settings button. In the new design each screen manages its own header area (Capture is dark/transparent, Note has no gear, Home/Sources/Ideas/Index have the gear in their own top bar). The global <header> block in App.jsx is removed as part of Branch 1. Each screen component is responsible for rendering its own top-bar row.


4. Screen Specifications

4.1 Home Screen (HomeScreen.jsx)

Layout (top to bottom, full height):

  • Top third: Three equal-width stat boxes in a row. Each is a tappable button.
    • Notes count → navigates to index
    • Ideas count → navigates to active-ideas
    • Sources count → navigates to sources
    • Boxes: --color-bg-input fill, --color-border border, --radius-card corners. Number in --text-display-size bold, label in --text-caption-size muted. Each box includes a <CaretRight weight="light" size={14} /> icon (Phosphor React) in the bottom-right corner to signal tappability — boxes must not look like static counters.
  • Middle (flex:1): Logo placeholder area — “Surfc” wordmark in --text-display-size bold + tagline in --text-caption-size muted, centred vertically. This area intentionally has breathing room for an eventual logo asset.
  • Bottom: Single “Start Capturing” CTA button, full width, --color-accent background, --color-text-primary text (Deep Walnut #1A1410), bold. Pinned above --safe-bottom.
  • Top-right: Gear icon opens Settings.
  • No bottom nav bar.

Props added to HomeScreen: sourcesCount (integer), onGoToSources, onGoToActiveIdeas.

4.2 Capture Screen (CaptureScreen.jsx)

No structural changes to the viewfinder layout. Changes:

  • The Capture screen’s own internal bottom nav is removed — the shared bottom nav in App.jsx is used instead (consistent with all other screens).
  • The status bar area above the viewfinder uses a transparent overlay. System clock and signal icons float over the dark camera feed. This works with viewport-fit=cover already declared in index.html per DESIGN.md §6.
  • On shutter tap: triggers the card slide animation (Branch 2) and immediately navigates to note view. onImageReady callback in useNoteForm is updated to call goToNote directly instead of goToReview.

4.3 New Note Screen (NoteForm.jsx)

Focused, single-purpose screen. Changes from current implementation:

Removed:

  • The “Add Source” section (books grid, title/author inputs, Add button) at the top.
  • The “Library” tab toggle at the top of the main column.
  • The onDiscoverIdeas manual trigger button.
  • The hideCapture prop logic (capture zone is gone from this screen entirely).

Added / changed:

  • Source field becomes a <select> dropdown populated from books state. Placeholder: — Select source —. Below the dropdown: a small + Add new source text link that navigates to the sources screen.
  • Note state persistence on source navigation: When the user taps + Add new source and navigates to sources, the NoteForm panel must remain mounted but hidden (CSS mobile-hidden class) — it must not be unmounted. Because useNoteForm is a hook in App.jsx, all form state (transcribed text, captured image, page number, pending tags) survives navigation naturally. No Dexie draft logic is required. This is the same pattern used by all other panels today. On returning to note view, the form is exactly as the user left it.
  • Auto-discover fires automatically after successful transcription (see Rule 5 in §7). The ”✦ Discovering ideas…” status line appears below the text area while in-flight. No button shown during this phase.
  • Manual discover fallback: A visible but understated “Discover Ideas” button appears only when !hasAutoDiscovered && !aiLoading && noteText.trim(). This covers the manual-entry path where no photo was taken.
  • Inline transcription error (replacing ReviewScreen): when transcription fails, a warm banner appears above the text area using --color-warning-bg / --color-warning tokens. Copy: “We couldn’t quite read that passage. Would you like to try again or type it manually?” Two actions: <ArrowCounterClockwise weight="light"/> Retake (navigates back to capture, clears image state but preserves any manually typed text) and a “Type manually” link (dismisses the banner, focuses the text area).
  • Cancel: Top-right <X weight="light"/> (Phosphor). Navigates back to the previous view. useUI tracks a previousNoteView (set to 'capture' or 'index' whenever goToNote is called), mirroring the existing previousMobileView pattern used by related-notes. Does not clear text — user may return.
  • Save Note button: --color-accent background, --color-text-primary text (#1A1410), bold. Dimmed (opacity: 0.5) while transcription or discover is in-flight. Active when noteText.trim() is non-empty and no loading state is active.
  • No settings gear icon on this screen.

4.4 Sources Screen (SourcesScreen.jsx) — NEW

Extracted from NoteForm. Manages the books list independently.

Layout:

  • Page title “Sources” (--text-display-size) + subtitle “Your reading library” (--text-caption-size muted).
  • Add form card at top: Title input (full width), Author input + Add button (row). Same behaviour as current addBook / onAddBook in useNoteForm.
  • Scrollable list of existing sources below. Each row: title (--text-title-size bold) + author (--text-caption-size). Right side: “remove” text link in --color-destructive.
  • Top-right: Gear icon.
  • Standard 3-button bottom nav. Home button active (this screen is a child of Home).

Data: Reads from books state (from useAuth). Writes call saveBook / deleteBook from db.js and cloudWrite — identical to existing flow, just in a new component.

4.5 Active Ideas Screen (IdeasScreen.jsx) — NEW

A focused view of ideas that have at least one note tagged to them.

Layout:

  • Page title “Ideas” + subtitle showing active count and sort rule (e.g. “7 active · sorted by note volume”).
  • Scrollable list of idea rows, sorted descending by note count.
  • Each row: coloured dot (amber --color-accent for Adler ideas, green --color-custom for custom), idea name (--text-title-size bold), note-count badge (amber chip for Adler, green chip for custom).
  • Tapping a row: calls filterByIdea(idea) then navigates to index. The Index filter strip will have that idea pre-selected.
  • Empty state (no notes yet): a single centred message — “Capture your first note to see ideas here.”
  • Top-right: Gear icon.
  • Standard 3-button bottom nav. Home button active.

Data: Derived from notes state. No new data operations — computed the same way IdeaFilterStrip computes activeIdeas in IndexScreen, but sorted by ideaCounts[idea] descending.

4.6 Index Screen (IndexScreen.jsx)

No structural changes. One targeted change:

  • The IdeaFilterStrip already shows only active ideas (ideas with ≥1 note). No code change required.
  • Add the Gear icon to the top-right header area (currently absent on this screen).

5. Capture Animation Flow (Branch 2)

State sequence

[Capture screen]
↓ shutter tap
[Card slide animation — ~400ms, on Capture screen]
↓ animation completes
[Note screen — transcription in-flight, ink bleed animating]
↓ transcription + discover complete
[Note screen — ready, Save active]
↓ Save tap
[Index screen — new note at top, toast confirmation]

Animation 1 — Card slide-down

Trigger: Immediately on shutter tap, before API call starts.
Purpose: Visual beat while Dexie writes the base64 image.
Implementation:

  • The captured image preview renders as a card with --radius-card shape.
  • Animates via transform: translateY(Xpx) scale(0.3) to the bottom of the screen, mimicking an index card being filed into a box.
  • Duration: ~400ms, cubic-bezier(0.4, 0, 0.8, 0.6) (ease-in, accelerates downward).
  • border-radius is maintained throughout via --radius-card on the element (not the transform).
  • Implemented as a CSS @keyframes — no JS animation loop.

Animation 2 — Ink bleed (text)

Trigger: Only fires on transcription success. Skipped entirely on failure.
Purpose: Words appear as if ink is drying on the page.
Implementation:

  • Transcribed text is split into word groups (approx. 3–5 words each).
  • Each group is wrapped in a <span> with animation-delay staggered at 50ms intervals.
  • Keyframe: opacity: 0 → 1, filter: blur(2px) → blur(0).
  • Duration per group: 300ms. Total for a ~30-word passage: ~800ms.
  • Uses filter: blur() on individual spans, not on the textarea or its parent — OCR integrity is unaffected (FUNCTIONAL.md Rule 3).
  • Save button timing: The Save button becomes active and fully opaque as soon as transcribeLoading is false and noteText.trim() is non-empty — it does not wait for the ink bleed animation to complete. Because the transcribed text lands in React state before the animation runs, the disabled / opacity logic is decoupled from the CSS animation. A user in a hurry can tap Save immediately after the first word appears.

Animation 3 — Idea chip fade-in

Trigger: Each chip as it arrives from discoverIdeas.
Implementation:

  • opacity: 0 → 1 + transform: translateY(4px) → translateY(0).
  • Duration: 200ms per chip, ease-out.

6. What Is Not Changing

  • IndexScreen.jsx layout and card design (confirmed — user happy with existing look).
  • db.js — no schema changes, no new stores.
  • supabase.js — no changes.
  • api.js — no changes. callTranscribeImage and callDiscoverIdeas are called identically; only the trigger point changes.
  • AuthScreen.jsx, all action sheets, edit forms, settings modal — untouched.
  • tokens.css — no new tokens needed. All animations use existing colour and radius tokens.
  • DESIGN.md — no changes needed; all new patterns are consistent with existing rules.
  • Desktop layout — this entire spec is mobile-only.

7. Updated FUNCTIONAL.md Contracts

The following additions and updates apply to FUNCTIONAL.md.

Rule 3 — updated (Ingestion Pipeline):
The Capture UI feeds the raw photo into ingest(photoAdapter). ReviewScreen is removed — on capture, the app transitions directly to the Note form. Transcription state (transcribeLoading, transcribeError) is managed inline on the Note form. The film grain overlay must not block access to the raw video stream.

Rule 5 — Auto-Discover Chaining (new):
When transcription succeeds, discoverIdeas must be triggered automatically, exactly once per capture event, without user input. A hasAutoDiscovered boolean flag on useNoteForm prevents re-triggering on subsequent text edits. If transcription fails or produces no text, discoverIdeas must not fire. On the manual entry path (no photo taken), a visible “Discover Ideas” button remains available as a fallback and is shown when !hasAutoDiscovered && !aiLoading && noteText.trim().

Rule 6 — Sources Screen Contract (new):
All book/source CRUD is performed through db.js (saveBook, deleteBook) and cloudWrite, identical to the existing flow. SourcesScreen is a UI extraction only — no new data operations or Dexie stores. The source <select> dropdown on the Note form and the Sources screen both read from the shared books state provided by useAuth.

Rule 7 — Navigation Contract (new):
The default mobileView on app launch is 'capture'. Valid view states are: home, capture, note, index, sources, active-ideas, related-notes. The state 'review' is removed. The Home Screen is the only screen that does not render the shared 3-button bottom nav.

Rule 8 — Animation Contract (new):
All animations are implemented as CSS transition or @keyframes only — no JavaScript animation loops, no requestAnimationFrame polling. No animation may block the UI thread during an active API call. The ink bleed animation fires only on transcription success. The card slide animation fires on every shutter tap regardless of transcription outcome. Animations must not apply filter or transform to the raw <video> element used by the camera.

Rule 9 — Error Copy Contract (new):
Transcription errors use --color-warning-bg and --color-warning tokens (warm amber). The --color-destructive token (red) is reserved for delete actions only. Error messages exposed to the user must use plain language. The approved copy for a transcription failure is: “We couldn’t quite read that passage. Would you like to try again or type it manually?“


8. Critical Implementation Constraints

These constraints are non-negotiable and must be enforced across both branches.

  1. Dexie-first writes — Every write (note save, book add/delete) must hit db.js before any cloud operation. The outbox-first sync pattern from FUNCTIONAL.md §2 is unchanged.
  2. Capture-First launch stateuseUI.js must initialise mobileView to 'capture', not 'home'.
  3. Phosphor React, light weight only — All new icons use weight="light". No solid, bold, or duotone weights anywhere in the redesigned screens.
  4. Auto-discover gatingdiscoverIdeas is triggered in useNoteForm only after a confirmed transcription success (non-empty text from ingest). The hasAutoDiscovered flag prevents any re-trigger. Transcription failure or empty result must short-circuit before discoverIdeas is called.
  5. NoteForm panel must not unmount — When mobileView is sources or active-ideas (reachable from the note flow), the note panel must remain in the DOM with mobile-hidden CSS, not conditionally removed from the React tree.

9. Files Changed Summary

Modified

| File | Nature of change |

|---|---| | src/App.jsx | Default view 'capture', new nav states, bottom nav restructure, chain API, remove ReviewScreen rendering | | src/hooks/useUI.js | Default state 'capture', add goToSources, goToActiveIdeas, add previousNoteView tracking, remove goToReview | | src/hooks/useNoteForm.js | Add hasAutoDiscovered flag, auto-trigger discoverIdeas on transcription success, update onImageReady to goToNote | | src/components/HomeScreen.jsx | Stat boxes, logo placeholder, single CTA, no bottom nav | | src/components/CaptureScreen.jsx | Remove internal nav, update shutter to trigger slide animation | | src/components/NoteForm.jsx | Remove Library section, source as dropdown, auto-discover, inline error, Phosphor icons | | src/components/IndexScreen.jsx | Add Gear icon to header | | src/styles.css | Slide animation, ink bleed, chip fade-in, stat box styles, new screen styles | | FUNCTIONAL.md | Rules 3 updated, Rules 5–9 added |

New

FileDescription
src/components/SourcesScreen.jsxSources manager, extracted from NoteForm
src/components/IdeasScreen.jsxActive ideas list, sorted by note count

Deleted

FileReason
src/components/ReviewScreen.jsxReplaced by inline transcription state on NoteForm