11 KiB
title, type, created, status, baseline_commit, completedDate, context, note
| title | type | created | status | baseline_commit | completedDate | context | note | ||
|---|---|---|---|---|---|---|---|---|---|
| US-4 Redesign — Embedded Structured View Block (replaces authors/works demo) | refactor | 2026-05-27 | done | CURRENT | 2026-05-30 |
|
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
notebookIdfrom 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
databaseBlocknodes 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
DatabaseBlockEditorordatabase-block-types.tspattern. - Use
revalidatePathfor 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 nodememento-note/components/database-block-editor.tsx-- DELETE — legacy authors/works editormemento-note/lib/editor/database-block-types.ts-- DELETE — legacy types with Verne/Cixin datamemento-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 rendersNotesStructuredTableorNotesGalleryViewin read-only mode (or editable if founder approves inline edit)memento-note/components/note-editor/note-content-area.tsx-- MODIFY — passnotebookId={note.notebookId}to<RichTextEditor>memento-note/components/rich-text-editor.tsx-- MODIFY — acceptnotebookId?: stringprop; pass it to editor storage; registerStructuredViewBlockExtensionin place ofDatabaseBlockExtension; update slash command entry (label, keywords, handler); update block action menu's "Transform into" optionmemento-note/components/block-action-menu.tsx-- MODIFY — replacedatabaseoption withstructuredView; import from new extensionmemento-note/locales/en.json+fr.json-- MODIFY — add keys:structuredViewBlock.insertLabel,structuredViewBlock.insertDesc,structuredViewBlock.noSchema,structuredViewBlock.noNotebook,structuredViewBlock.openInNotebook,structuredViewBlock.displayModeTable,structuredViewBlock.displayModeGallery; remove alldatabaseBlock.*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 definitionsmemento-note/components/database-block-editor.tsx-- DELETE file -- removes authors/works editor UImemento-note/components/tiptap-database-block-extension.tsx-- DELETE file -- removes legacy TipTap node extensionmemento-note/components/tiptap-structured-view-block-extension.tsx-- CREATE — TipTap NodestructuredViewBlockwith 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 — addnotebookId={note.notebookId ?? undefined}prop to both<RichTextEditor>instancesmemento-note/components/rich-text-editor.tsx-- MODIFY — (a) addnotebookId?: stringto props, store ineditor.storage.structuredViewBlock = { notebookId }; (b) swapDatabaseBlockExtensionforStructuredViewBlockExtension; (c) update slash entry: title keystructuredViewBlock.insertLabel, description keystructuredViewBlock.insertDesc, keywords['vue', 'tableau', 'structuré', 'structured', 'view', 'database', 'db']; (d) update slash handler to callinsertStructuredViewBlockAtSelection(editor, notebookId)memento-note/components/block-action-menu.tsx-- MODIFY — replacedatabasetransform option withstructuredView; update importmemento-note/locales/en.json+fr.json-- MODIFY — add new i18n keys (see Code Map); removedatabaseBlock.*keys
Acceptance Criteria:
- Given a note in a structured notebook, when user types
/vueor/structured, then astructuredViewBlocknode 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
/vueis triggered, then the block shows an inline "requires a notebook" message. - Given a note with a legacy
data-database-blocknode, 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
structuredViewBlockentry 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 Momento'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.