Skip to content

UI Architecture

UI Architecture

Skill in use: ui-architecture — documenting entry points, composition, state, and PWA behaviors from code.

1. UI entry points

  • src/main.jsx mounts <App /> inside React.StrictMode and imports tokens.css + styles.css, so all UI and theming originate here.
  • src/App.jsx is the single page shell: it gates auth (AuthScreen), readiness, and renders the multi-pane layout plus Settings/action sheets; there is no router or lazy-loading boundary.
  • Auth flows originate in src/AuthScreen.jsx (three-zone layout post-SUR-261: whitespace top → centred <HomeHero /> → CTA stack pinned above safe-area inset). The primary CTA calls signInWithGoogle() from src/supabase.js; a secondary “Use a different account” CTA opens src/components/BottomSheet.jsx containing the email-OTP sign-in flow, which calls requestEmailOtp(email) then verifyEmailOtp(email, token) (no signUpshouldCreateUser:false rejects unknown emails with a friendly waitlist CTA). Platform detection (TWA / standalone PWA → 6-digit code; desktop → magic link) lives in src/lib/platform.js. The hero is the shared <HomeHero /> component (src/components/HomeHero.jsx), also used by HomeScreen.jsx. The resulting session is handed back to <App />.
  • Shared hooks such as useAuth, useUI, useNoteForm, useSettings, useNoteActions, and useToast (all under src/hooks/) act as service locators for state; components receive pre-wired props rather than owning data fetching.
  • When window.location.pathname === '/how-it-works', <App /> short-circuits all other routing logic and renders src/components/HowItWorksPage.jsx, feeding it the structured content defined in src/content/howItWorksContent.js so copy/media placeholders live in one file.
  • LandingPage.jsx (logged-out marketing surface) and SettingsModal.jsx (in-app help) now expose a How Surfc works link that deep-links to /how-it-works?source=landing|settings while recording entry events via useAnalytics.

2. Screen/module structure

flowchart LR
App[src/App.jsx] -->|auth gating| AuthScreen[src/AuthScreen.jsx]
App -->|state props| Home[components/HomeScreen.jsx]
App --> Capture[components/CaptureScreen.jsx]
App --> Review[components/ReviewScreen.jsx]
App --> NoteForm[components/NoteForm.jsx]
App --> Library[components/Library.jsx]
App --> Index[components/IndexScreen.jsx]
App --> Sidebar[components/IdeasSidebar.jsx]
App --> Detail[components/IdeaDetail.jsx]
App --> Settings[components/SettingsModal.jsx]
App --> NoteAction[components/NoteActionSheet.jsx]
App --> IdeaAction[components/IdeaActionSheet.jsx]
  • Screens: HomeScreen, CaptureScreen, ReviewScreen, and IndexScreen map to the mobile tab states managed by useUI (mobileView state in src/hooks/useUI.js). Desktop rendering keeps the sidebar (IdeasSidebar) and detail pane (IdeaDetail) persistently visible, while mobile swaps panels via conditional rendering inside App.
  • Notes + Library: NoteForm (new note composer) and Library (list view using NoteCard.jsx) share prop-driven dependencies for books, notes, tagging, and callbacks.
  • Idea interactions: IdeasSidebar surfaces canonical + custom ideas and uses useLongPress (src/hooks/useLongPress.js) to open edit/delete sheets; IdeaDetail.jsx shows counts, note excerpts, and rediscovery triggers via props.
  • Settings + modals: SettingsModal.jsx is always rendered but hidden when showSettings is false; it multiplexes account info, API key editing, custom idea CRUD, and import/export. NoteActionSheet.jsx, NoteEditForm.jsx, IdeaActionSheet.jsx, and IdeaEditForm.jsx are toggled via state variables in App.jsx.
  • Navigation controls: Bottom nav buttons in App.jsx call useUI helpers (goToHome, goToCapture, goToIndex, etc.), so navigation is purely stateful rather than URL-based.
  • Public help surface: HowItWorksPage.jsx consumes the HOW_IT_WORKS_CONTENT object to render hero/section/FAQ/CTA blocks with responsive .hiw-* styles (see src/styles.css). The component keeps anonymous access by reading, but not requiring, useAuth session state and drives PostHog events (how_it_works_viewed, how_it_works_cta_click) for marketing attribution.

3. State and data access patterns

  • useAuth (src/hooks/useAuth.js) is the root state provider: it loads Dexie via loadAll, tracks Supabase session/online status, triggers sync (see syncFromCloud), and exposes books, notes, customIdeas, apiKey, plus a cloudWrite helper to downstream hooks.
  • useUI (src/hooks/useUI.js) owns presentation-only state such as mobileView, selectedIdea, search text, settings modal, and lightbox image; it derives ideaCounts and notesForIdea from current notes.
  • useNoteForm (src/hooks/useNoteForm.js) composes Dexie mutations (saveBook, saveNote), Supabase writes (via cloudWrite and uploadImage), and AI calls (callTranscribeImage, callDiscoverIdeas). It threads callbacks (goToLibrary, selectIdea) supplied by useUI, so the hook bridges local state, sync, and AI ingestion explicitly.
  • useSettings (src/hooks/useSettings.js) couples Dexie operations (custom idea CRUD, import/export, API key persistence) with UI flows; renaming custom ideas cascades through dbUpdateNote and syncPendingTagRename.
  • useNoteActions (src/hooks/useNoteActions.js) provides note edit/delete/rediscover behaviors, combining Dexie updates, cloudWrite, and additional AI tagging when “Rediscover Ideas” is used.
  • Toast notifications are managed by useToast (src/hooks/useToast.js, a simple timer state) and displayed at the bottom of App.
  • useAnalytics (src/hooks/useAnalytics.js) wraps posthog-js/react; LandingPage, SettingsModal, and HowItWorksPage emit how_it_works_entry_click, how_it_works_viewed, and how_it_works_cta_click events to PostHog for attribution.
  • Because there is no context/provider system, <App /> calls each hook once and threads dozens of props to leaf components, keeping data ownership centralized but increasing prop drilling.

4. PWA behavior

  • vite.config.js registers @vitejs/plugin-react plus VitePWA with registerType: 'autoUpdate', Google Fonts runtime caches, and a manifest (standalone display, custom theme/background colors, maskable icons).
  • netlify.toml configures SPA redirects and manifest headers so the PWA works when deployed to Netlify’s static hosting.
  • Online/offline indicators rely on navigator.onLine and window events in useAuth; reconnect triggers syncFromCloud, which first flushes local writes then refetches Supabase tables (src/hooks/useAuth.js).
  • Service worker registration is delegated to VitePWA (vite.config.js with registerType: 'autoUpdate'), so there is no custom background sync or push logic in src/main.jsx.
  • Asset caching beyond fonts uses the default Workbox precache glob (**/*.{js,css,html,ico,png,svg,woff2}), meaning note images remain device-local until uploaded to Supabase (and downloaded again on other devices).
  • Anthropic API calls are performed client-side via fetch, so the PWA works offline for capture+queueing but requires connectivity for transcription/tagging.

5. Risks and refactor opportunities

  • Single monolithic shell: src/App.jsx mixes layout, navigation, modal orchestration, and lightbox logic; failures ripple across every screen.
  • Hook bloat: src/hooks/useNoteForm.js and src/hooks/useSettings.js combine UX state with Dexie, Supabase, and Anthropic calls, making the coupling between local state, sync, and AI logic harder to reason about.
  • Prop drilling: Without React context providers for collections or UI state, leaf components receive long prop lists; adding context stores (e.g., notes/books provider) would shrink the surface area.
  • PWA opacity: Reliance on vite-plugin-pwa defaults hides service-worker lifecycle events; for a sync-first tool it would help to expose update prompts or background sync status.
  • Security exposure: Direct Anthropic calls (src/api.js) require users to paste an API key; the UI never warns that the key lives in IndexedDB (db.meta).
  • Action sheets vs. accessibility: Long-press interactions implemented via useLongPress (src/hooks/useLongPress.js) lack keyboard fallbacks, so desktop accessibility is limited.