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:
- Home Screen was a dead end — now a live dashboard with navigable stat boxes.
- ReviewScreen created an unnecessary “Continue” hurdle — removed entirely.
- 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
| State | Screen | Notes |
|---|---|---|
capture | Camera viewfinder | Default on launch |
home | Stat box dashboard | Accessible from bottom nav |
index | Notes list | Accessible from bottom nav |
note | New note form | Via shutter tap or Write Manually |
sources | Sources manager | Via Home stat box — NEW |
active-ideas | Active ideas list | Via Home stat box — NEW |
related-notes | Related notes | Via note long-press — unchanged |
review | Transcription review | REMOVED |
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-inputfill,--color-borderborder,--radius-cardcorners. Number in--text-display-sizebold, label in--text-caption-sizemuted. 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.
- Notes count → navigates to
- Middle (flex:1): Logo placeholder area — “Surfc” wordmark in
--text-display-sizebold + tagline in--text-caption-sizemuted, centred vertically. This area intentionally has breathing room for an eventual logo asset. - Bottom: Single “Start Capturing” CTA button, full width,
--color-accentbackground,--color-text-primarytext (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.jsxis 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=coveralready declared inindex.htmlper DESIGN.md §6. - On shutter tap: triggers the card slide animation (Branch 2) and immediately navigates to
noteview.onImageReadycallback inuseNoteFormis updated to callgoToNotedirectly instead ofgoToReview.
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
onDiscoverIdeasmanual trigger button. - The
hideCaptureprop logic (capture zone is gone from this screen entirely).
Added / changed:
- Source field becomes a
<select>dropdown populated frombooksstate. Placeholder:— Select source —. Below the dropdown: a small+ Add new sourcetext link that navigates to thesourcesscreen. - Note state persistence on source navigation: When the user taps
+ Add new sourceand navigates tosources, the NoteForm panel must remain mounted but hidden (CSSmobile-hiddenclass) — it must not be unmounted. BecauseuseNoteFormis a hook inApp.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 tonoteview, 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-warningtokens. 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 tocapture, 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.useUItracks apreviousNoteView(set to'capture'or'index'whenevergoToNoteis called), mirroring the existingpreviousMobileViewpattern used byrelated-notes. Does not clear text — user may return. - Save Note button:
--color-accentbackground,--color-text-primarytext (#1A1410), bold. Dimmed (opacity: 0.5) while transcription or discover is in-flight. Active whennoteText.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-sizemuted). - Add form card at top: Title input (full width), Author input + Add button (row). Same behaviour as current
addBook/onAddBookinuseNoteForm. - Scrollable list of existing sources below. Each row: title (
--text-title-sizebold) + 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-accentfor Adler ideas, green--color-customfor custom), idea name (--text-title-sizebold), note-count badge (amber chip for Adler, green chip for custom). - Tapping a row: calls
filterByIdea(idea)then navigates toindex. 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
IdeaFilterStripalready 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-cardshape. - 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-radiusis maintained throughout via--radius-cardon 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>withanimation-delaystaggered 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
transcribeLoadingisfalseandnoteText.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, thedisabled/ 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.jsxlayout 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.callTranscribeImageandcallDiscoverIdeasare 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.
- Dexie-first writes — Every write (note save, book add/delete) must hit
db.jsbefore any cloud operation. The outbox-first sync pattern from FUNCTIONAL.md §2 is unchanged. - Capture-First launch state —
useUI.jsmust initialisemobileViewto'capture', not'home'. - Phosphor React, light weight only — All new icons use
weight="light". No solid, bold, or duotone weights anywhere in the redesigned screens. - Auto-discover gating —
discoverIdeasis triggered inuseNoteFormonly after a confirmed transcription success (non-emptytextfromingest). ThehasAutoDiscoveredflag prevents any re-trigger. Transcription failure or empty result must short-circuit beforediscoverIdeasis called. - NoteForm panel must not unmount — When
mobileViewissourcesoractive-ideas(reachable from the note flow), the note panel must remain in the DOM withmobile-hiddenCSS, 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
| File | Description |
|---|---|
src/components/SourcesScreen.jsx | Sources manager, extracted from NoteForm |
src/components/IdeasScreen.jsx | Active ideas list, sorted by note count |
Deleted
| File | Reason |
|---|---|
src/components/ReviewScreen.jsx | Replaced by inline transcription state on NoteForm |