22
33## Overview
44
5- Memo is a Notion-style workspace app: block-based editor, nested pages, workspaces,
6- real-time collaboration . Built with Next.js 16 on Vercel, Supabase for data and auth,
7- Sentry for error tracking.
5+ Memo is a Notion-style workspace app: Lexical block editor, nested pages, personal +
6+ team workspaces, member invitations . Built with Next.js 16 on Vercel, Supabase for
7+ data and auth, Sentry for error tracking. Realtime collaboration is deferred to post-MVP .
88
99## System Diagram
1010
1111```
1212Browser
1313 ├── Server Components → Supabase server client (read data, cookie-based auth)
14- ├── Client Components → Supabase browser client (mutations, realtime subscriptions )
14+ ├── Client Components → Supabase browser client (mutations, Lexical editor )
1515 ├── API Routes (/api/*) → server-side logic, health checks
1616 └── Proxy (src/proxy.ts) → Supabase session refresh, route protection
1717
1818Supabase
19- ├── PostgreSQL → workspaces, pages, blocks, members
20- ├── Auth → GitHub/Google OAuth, email/password
21- ├── Realtime → live collaboration (page edits, presence)
22- └── RLS → row-level security per workspace
19+ ├── PostgreSQL → profiles, workspaces, members, workspace_invites, pages
20+ ├── Auth → email/password (OAuth deferred — buttons rendered with "coming soon")
21+ ├── Storage → image uploads for editor
22+ ├── DB Triggers → handle_new_user (profile + personal workspace + owner membership)
23+ └── RLS → row-level security per workspace membership
2324
2425Sentry → error tracking, source maps, performance monitoring, session replay
2526Vercel → hosting, preview deploys per PR, production deploys on merge
@@ -29,45 +30,123 @@ Vercel → hosting, preview deploys per PR, production deploys on merge
2930
3031No database migrations exist yet. The schema below is the planned target model.
3132Create migrations in ` supabase/migrations/ ` as features are implemented.
33+ See ` docs/product-spec.md ` → Data Model for the full column-level schema.
3234
3335```
34- workspace
35- ├── has many: members (user_id + role)
36+ profiles (1:1 with auth.users)
37+ └── created automatically on sign-up via handle_new_user trigger
38+
39+ workspaces
40+ ├── is_personal: boolean (personal workspace = non-deletable, always listed first)
41+ ├── created_by → profiles.id
42+ ├── Constraint: max 3 workspaces per user (created_by count, enforced via DB trigger)
43+ ├── Constraint: one personal workspace per user (partial unique index)
44+ ├── has many: members (user_id + role: owner | admin | member)
45+ ├── has many: workspace_invites (email + role + token)
3646 └── has many: pages
37- ├── belongs to: workspace
38- ├── has one: parent_page (nullable → enables nesting)
39- └── has many: blocks (ordered by position)
40- ├── type: text | heading_1 | heading_2 | heading_3 | bullet_list |
41- │ numbered_list | todo | code | image | divider | callout | toggle
42- └── content: JSON (structure varies by type)
47+ ├── parent_id → pages.id (nullable, enables nesting)
48+ ├── content: jsonb (Lexical editor state — NOT a separate blocks table)
49+ ├── position: integer (ordering among siblings)
50+ └── created_by → profiles.id
51+
52+ Sign-up flow (atomic, via DB trigger):
53+ 1. auth.users row created by Supabase Auth
54+ 2. handle_new_user trigger fires → creates:
55+ a. profiles row
56+ b. workspaces row (is_personal = true, name = "{display_name}'s Workspace")
57+ c. members row (role = owner)
4358```
4459
4560## Key Technical Decisions
4661
4762| Decision | Choice | Rationale |
4863| ---| ---| ---|
49- | Editor library | Tiptap or BlockNote (evaluate before building) | Do NOT build a custom editor — this is months of work |
50- | Content storage | JSON block tree in PostgreSQL | Flexible, queryable, no HTML parsing needed |
51- | Auth | Supabase Auth with RLS | Row-level security at the DB layer, no app-level auth checks needed per query |
52- | Realtime | Supabase Realtime subscriptions | Built into the client, no extra infrastructure |
64+ | Editor library | ** Lexical** (Meta, MIT) | Full control, MIT license, Meta-backed. Build from lexical-playground reference, adapt to Tailwind + shadcn/ui. |
65+ | Content storage | Lexical JSON in PostgreSQL ` jsonb ` | ` editorState.toJSON() ` stored in ` pages.content ` . No separate blocks table. |
66+ | Auth | Supabase Auth — email/password | OAuth (GitHub, Google) deferred to post-MVP. Buttons rendered with "coming soon" tooltip. |
67+ | Workspace model | Personal + team workspaces | Auto-created personal workspace on sign-up (non-deletable). Max 3 created workspaces per user. Unlimited joined via invite. |
68+ | Realtime | Deferred to post-MVP | Yjs + Supabase Realtime adds complexity. Ship single-user editing first. |
5369| Styling | Tailwind v4 + shadcn/ui | No custom CSS, consistent design system |
5470| Package manager | pnpm | Strict dependency resolution, faster installs |
5571| Session management | Next.js 16 proxy (not middleware) | ` src/proxy.ts ` with ` updateSession ` — Next.js 16 convention replacing middleware |
72+ | Floating UI | ` @floating-ui/react ` | Positioning for slash command menu, floating toolbar, link editor (same as Lexical playground) |
73+ | Image storage | Supabase Storage | Bucket for uploaded images, public URL stored in ImageNode |
74+ | Full-text search | PostgreSQL ` tsvector ` + ` tsquery ` | Generated column or trigger on page title + extracted content text |
75+
76+ ## Lexical Editor — Implementation Plan
77+
78+ The Lexical playground (` facebook/lexical/packages/lexical-playground ` ) is the reference.
79+ We do NOT fork it. We build a Memo-specific editor using official Lexical packages,
80+ referencing the playground for patterns. All UI rebuilt with Tailwind + shadcn/ui.
81+
82+ ### Official Lexical packages (install from npm)
83+
84+ | Package | Purpose |
85+ | ---| ---|
86+ | ` lexical ` | Core editor engine |
87+ | ` @lexical/react ` | React bindings (LexicalComposer, plugins, hooks) |
88+ | ` @lexical/rich-text ` | Headings, quotes, rich text support |
89+ | ` @lexical/list ` | Bullet, numbered, and check lists |
90+ | ` @lexical/code ` | Code blocks |
91+ | ` @lexical/link ` | Link nodes and auto-linking |
92+ | ` @lexical/selection ` | Selection utilities |
93+ | ` @lexical/utils ` | Shared utilities |
94+ | ` @lexical/markdown ` | Markdown import/export transforms |
95+ | ` @lexical/clipboard ` | Copy/paste handling |
96+
97+ Pin to a specific version to avoid breaking changes.
98+
99+ ### Custom plugins (referencing playground)
100+
101+ | Plugin | Playground reference | Complexity |
102+ | ---| ---| ---|
103+ | ComponentPickerPlugin (slash commands) | ` plugins/ComponentPickerPlugin ` | Medium |
104+ | DraggableBlockPlugin (drag-and-drop) | ` plugins/DraggableBlockPlugin ` | High |
105+ | FloatingTextFormatToolbarPlugin | ` plugins/FloatingTextFormatToolbarPlugin ` | High |
106+ | FloatingLinkEditorPlugin | ` plugins/FloatingLinkEditorPlugin ` | Medium |
107+ | ImagesExtension | ` plugins/ImagesExtension ` | High |
108+ | CodeHighlightExtension | ` plugins/CodeHighlightExtension ` | Medium |
109+ | CollapsibleExtension (toggle blocks) | ` plugins/CollapsibleExtension ` | Medium |
110+ | ToolbarPlugin (top toolbar) | ` plugins/ToolbarPlugin ` | High |
111+
112+ ### Custom nodes
113+
114+ | Node | Type | Purpose |
115+ | ---| ---| ---|
116+ | ImageNode | DecoratorNode | Image display with caption, resize handles |
117+ | CalloutNode | ElementNode | Callout/alert block with emoji + text |
118+ | DividerNode | HorizontalRuleNode (` @lexical/extension ` ) | Horizontal divider |
119+
120+ ### Skipped plugins (not needed for MVP)
121+
122+ ExcalidrawPlugin, EquationsPlugin, PollPlugin, FigmaExtension, MentionsPlugin,
123+ SpeechToTextPlugin, AutocompletePlugin, CommentPlugin, TablePlugin, LayoutPlugin,
124+ DateTimeExtension, VersionsPlugin.
125+
126+ ### Content storage flow
127+
128+ ```
129+ Save: editor.getEditorState().toJSON() → Supabase pages.content (jsonb)
130+ Load: editor.setEditorState(editor.parseEditorState(json))
131+ Auto-save: debounce 500ms on editor change → write to Supabase
132+ ```
56133
57134## Request Flow
58135
591361 . User visits a page → proxy (` src/proxy.ts ` ) refreshes Supabase session via ` updateSession `
601372 . Server component renders with data from Supabase server client (` @/lib/supabase/server ` )
61- 3 . Client component hydrates, subscribes to Realtime for live updates
62- 4 . User edits a block → client component writes to Supabase → Realtime broadcasts to other users
138+ 3 . Client component hydrates, initializes Lexical editor with saved content from ` pages.content `
139+ 4 . User edits content → Lexical editor state changes → debounced auto-save writes ` editorState.toJSON() ` to Supabase
631405 . Errors captured by Sentry (client via ` instrumentation-client.ts ` , server via ` src/instrumentation.ts ` )
64141
65142## Component Map
66143
144+ Current state (infrastructure only — no product features yet):
145+
67146```
68147src/
69148├── app/ # Next.js App Router
70- │ ├── layout.tsx # Root layout (Geist fonts, global styles )
149+ │ ├── layout.tsx # Root layout (currently Geist fonts — will switch to JetBrains Mono )
71150│ ├── page.tsx # Landing page
72151│ ├── manifest.ts # PWA manifest (name, icons, display mode)
73152│ ├── global-error.tsx # Sentry error boundary
@@ -88,6 +167,37 @@ Root config files:
88167└── sentry.client.config.ts # Sentry client SDK config
89168```
90169
170+ Planned structure (added as features are built):
171+
172+ ```
173+ src/
174+ ├── app/
175+ │ ├── (auth)/ # Unauthenticated routes
176+ │ │ ├── sign-in/page.tsx # /sign-in
177+ │ │ ├── sign-up/page.tsx # /sign-up
178+ │ │ └── invite/[token]/page.tsx # /invite/[token]
179+ │ ├── (app)/ # Authenticated routes
180+ │ │ ├── layout.tsx # App shell (sidebar + main content)
181+ │ │ └── [workspaceSlug]/
182+ │ │ ├── page.tsx # /[workspaceSlug] (workspace home)
183+ │ │ ├── [pageId]/page.tsx # /[workspaceSlug]/[pageId] (editor)
184+ │ │ └── settings/
185+ │ │ ├── page.tsx # /[workspaceSlug]/settings
186+ │ │ └── members/page.tsx # /[workspaceSlug]/settings/members
187+ │ └── api/
188+ │ ├── health/ # Existing
189+ │ └── ... # Additional API routes as needed
190+ ├── components/
191+ │ ├── ui/ # shadcn/ui components
192+ │ ├── editor/ # Lexical editor + plugins
193+ │ ├── sidebar/ # Sidebar, page tree, workspace switcher
194+ │ └── ... # Feature-specific components
195+ ├── lib/
196+ │ ├── supabase/ # Existing
197+ │ └── types.ts # Shared TypeScript types
198+ └── ...
199+ ```
200+
91201## Observability
92202
93203- ** Sentry client** : session replay (10% normal, 100% on error), route transition tracking
0 commit comments