Files
Momento/_bmad-output/implementation-artifacts/spec-us4-db-inline-redesign.md
Antigravity 2272fa498a
Some checks failed
CI / Deploy production (on server) (push) Has been cancelled
CI / Lint, Unit Tests & Build (push) Has been cancelled
docs: mettre à jour spec US-4 structuredViewBlock inline redesign
2026-05-30 11:13:18 +00:00

118 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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.
---
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
## 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 |
</frozen-after-approval>
## 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 `<RichTextEditor>`
- `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 `<RichTextEditor>` 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 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.