--- title: 'US-4 Redesign — Embedded Structured View Block (replaces authors/works demo)' type: 'refactor' created: '2026-05-27' status: 'done' baseline_commit: 'CURRENT' completedDate: '2026-05-30' context: - 'docs/story-nextgen-editor-us4-redesign.md' - 'memento-note/lib/structured-views/types.ts' note: > Implementation complete with TWO modes: 1. Local Database (isLocal: true) — Notion-like inline table with editable columns/rows 2. Notebook Linked View (isLocal: false) — Live Structured Views integration Plus: Analytics panel, Memory Echo semantic search, Convert-to-notebook feature. Implementation exceeds original spec scope. --- ## Intent **Problem:** The current `/database` slash command inserts a self-contained "Authors & Works" relational block with hardcoded Jules Verne / Liu Cixin demo data stored as JSON blobs in TipTap node attributes — completely disconnected from Structured Views (NotebookSchema / NoteProperty), duplicating a data model and confusing users who expect a real notebook-linked view. **Approach:** Replace the current `databaseBlock` TipTap node and its three files (extension, editor component, types) with a new `structuredViewBlock` node that stores only a reference (`notebookId`, `displayMode`, `filter`), then reads live data from the existing Structured Views API — or falls back gracefully when the notebook has no schema. ## Boundaries & Constraints **Always:** - The block stores only a reference (`notebookId` + metadata attrs) — never serialises note rows/property values into TipTap HTML. - The editor must receive `notebookId` from its parent (`note-content-area.tsx`) for the block to resolve its schema. - i18n: all labels go through `useLanguage()` with keys in EN/FR minimum; no hardcoded strings. - Mutations (add note, edit property value) use optimistic updates via `NOTE_REQUEST_SAVE_EVENT` — no full revalidatePath. - If the current notebook has no `NotebookSchema`, show a contextual callout ("This notebook has no structured view — set one up from the notebook header") instead of a blank block; never auto-insert demo data. - Migration: silently drop legacy `databaseBlock` nodes on load (treat as unknown node, ProseMirror drops them or show an "outdated block" placeholder). - On delete of the block: only the TipTap reference node is removed; no Prisma records are affected. - RTL (fa, ar): the block wrapper must respect `dir="auto"`. **Ask First:** - Whether the block should support inline editing of property values (clicking a cell to edit) or be read-only with a "Open in notebook" CTA only — founder decides before coding begins. - Whether Kanban view is in scope for v1 inline (complex drag-and-drop inside a TipTap NodeView) or deferred to v2. **Never:** - Insert any demo data (no Jules Verne, no placeholders rows, no fake schema). - Create a second data storage system parallel to NotebookSchema/NoteProperty. - Store note rows or property values in TipTap node attributes. - Copy the `DatabaseBlockEditor` or `database-block-types.ts` pattern. - Use `revalidatePath` for mutations from inside the block. ## I/O & Edge-Case Matrix | Scenario | Input / State | Expected Output / Behavior | Error Handling | |----------|--------------|---------------------------|----------------| | Happy path — structured notebook | Note in structured notebook, user types `/vue` → insert block | Block renders table of notes with their schema properties | — | | No schema on notebook | Note is in a plain notebook (no NotebookSchema) | Block shows callout "Set up a structured view from the notebook header" with link | CTA links to notebook toolbar wizard | | Note not in any notebook | Note has no `notebookId` | Block shows "This block requires a notebook. Move this note to a notebook to use it." | Graceful inline message | | Legacy `databaseBlock` node found on load | HTML contains `data-database-block="true"` | ProseMirror skips the node (unknown type after extension removal); optionally show 1-line deprecation placeholder | No crash, no data loss on other content | | API error fetching schema | `GET /api/notebooks/:id/schema` 500 | Block shows error state with retry button | Console.error, no spinner freeze | | Inline edit (if in scope) — optimistic patch | User edits a cell → PATCH `/api/notes/:id/properties` | Optimistic update on cell, save triggered via NOTE_REQUEST_SAVE_EVENT | Rollback cell on API error | ## Code Map - `memento-note/components/tiptap-database-block-extension.tsx` -- **DELETE** — legacy authors/works TipTap node - `memento-note/components/database-block-editor.tsx` -- **DELETE** — legacy authors/works editor - `memento-note/lib/editor/database-block-types.ts` -- **DELETE** — legacy types with Verne/Cixin data - `memento-note/components/tiptap-structured-view-block-extension.tsx` -- **NEW** — TipTap Node extension (`structuredViewBlock`, attrs: `notebookId`, `displayMode: 'table'|'gallery'`, `filterJson`) - `memento-note/components/structured-view-block-embed.tsx` -- **NEW** — React NodeView component; fetches schema via SWR(`/api/notebooks/${notebookId}/schema`) and renders `NotesStructuredTable` or `NotesGalleryView` in read-only mode (or editable if founder approves inline edit) - `memento-note/components/note-editor/note-content-area.tsx` -- **MODIFY** — pass `notebookId={note.notebookId}` to `` - `memento-note/components/rich-text-editor.tsx` -- **MODIFY** — accept `notebookId?: string` prop; pass it to editor storage; register `StructuredViewBlockExtension` in place of `DatabaseBlockExtension`; update slash command entry (label, keywords, handler); update block action menu's "Transform into" option - `memento-note/components/block-action-menu.tsx` -- **MODIFY** — replace `database` option with `structuredView`; import from new extension - `memento-note/locales/en.json` + `fr.json` -- **MODIFY** — add keys: `structuredViewBlock.insertLabel`, `structuredViewBlock.insertDesc`, `structuredViewBlock.noSchema`, `structuredViewBlock.noNotebook`, `structuredViewBlock.openInNotebook`, `structuredViewBlock.displayModeTable`, `structuredViewBlock.displayModeGallery`; remove all `databaseBlock.*` keys (after verifying no other consumer) ## Tasks & Acceptance **Execution:** - [ ] `memento-note/lib/editor/database-block-types.ts` -- DELETE file -- eliminates Verne/Cixin hardcoded data and legacy type definitions - [ ] `memento-note/components/database-block-editor.tsx` -- DELETE file -- removes authors/works editor UI - [ ] `memento-note/components/tiptap-database-block-extension.tsx` -- DELETE file -- removes legacy TipTap node extension - [ ] `memento-note/components/tiptap-structured-view-block-extension.tsx` -- CREATE — TipTap Node `structuredViewBlock` with attrs `{ notebookId: string, displayMode: 'table'|'gallery', filterJson: string }`, `ReactNodeViewRenderer(StructuredViewBlockEmbed)` - [ ] `memento-note/components/structured-view-block-embed.tsx` -- CREATE — NodeView wrapper: SWR fetch schema, conditional rendering (no-schema callout / no-notebook callout / table or gallery view), RTL-safe wrapper, `dir="auto"` - [ ] `memento-note/components/note-editor/note-content-area.tsx` -- MODIFY — add `notebookId={note.notebookId ?? undefined}` prop to both `` instances - [ ] `memento-note/components/rich-text-editor.tsx` -- MODIFY — (a) add `notebookId?: string` to props, store in `editor.storage.structuredViewBlock = { notebookId }`; (b) swap `DatabaseBlockExtension` for `StructuredViewBlockExtension`; (c) update slash entry: title key `structuredViewBlock.insertLabel`, description key `structuredViewBlock.insertDesc`, keywords `['vue', 'tableau', 'structuré', 'structured', 'view', 'database', 'db']`; (d) update slash handler to call `insertStructuredViewBlockAtSelection(editor, notebookId)` - [ ] `memento-note/components/block-action-menu.tsx` -- MODIFY — replace `database` transform option with `structuredView`; update import - [ ] `memento-note/locales/en.json` + `fr.json` -- MODIFY — add new i18n keys (see Code Map); remove `databaseBlock.*` keys **Acceptance Criteria:** - Given a note in a structured notebook, when user types `/vue` or `/structured`, then a `structuredViewBlock` node is inserted showing the notebook's notes and their properties — no demo data appears. - Given the block is inserted, when the notebook has no schema, then the block shows a callout with a link to the notebook header wizard — no crash, no empty white box. - Given the note has no notebookId, when `/vue` is triggered, then the block shows an inline "requires a notebook" message. - Given a note with a legacy `data-database-block` node, when the note is opened, then the legacy block is silently dropped (or shown as deprecated placeholder) and the rest of the note content is intact. - Given the block is visible, when user resizes or the app language is Persian (fa, RTL), then the block layout is mirrored correctly. - Given the slash command menu opens, when user searches "database" or "db", then the new `structuredViewBlock` entry appears (backward-compatible keywords). - Given "Transform into" is opened in the block action menu, the "Database" option is replaced by "Vue structurée" / "Structured View". ## Spec Change Log ## Design Notes **Why reference-only attrs?** Storing note rows in TipTap HTML scales to O(notes × properties) per keystroke — unacceptable for perf. The reference pattern (`notebookId` only) is how Notion "linked database" blocks work and aligns with Memento's already-existing API layer. **notebookId propagation:** The editor currently receives only `noteId`. The smallest change is to add `notebookId` as a prop to `RichTextEditor` (already used by `note-content-area.tsx`). No context change needed. **Legacy migration:** ProseMirror silently drops unknown node types when the corresponding extension is not registered. Removing `DatabaseBlockExtension` from the extensions array is sufficient for new sessions. For existing notes with the old HTML, the `data-database-block` div will parse as an unrecognised block and ProseMirror will omit it. If this is too silent, add a `parseHTML` rule in the new extension that matches `div[data-database-block]` and converts it to a deprecated-placeholder paragraph. ## Verification **Commands:** - `npm run lint --prefix memento-note` -- expected: 0 errors (no imports from deleted files) - `npm run build --prefix memento-note` -- expected: build succeeds, no missing module errors **Manual checks (if no CLI):** - Open a note in a structured notebook → type `/vue` → confirm block appears with real notes (not Jules Verne). - Open a note in a plain notebook → type `/vue` → confirm "no schema" callout appears. - Open a note with a saved legacy `databaseBlock` → confirm note loads cleanly, no crash. - Switch app language to `fa` → confirm block layout is RTL-mirrored.