docs: accept adr 0004 rich-text editor decisions

This commit is contained in:
ginnoir
2026-07-04 19:20:29 -05:00
parent fb67692a08
commit 67f67525ff
2 changed files with 78 additions and 5 deletions
+77 -4
View File
@@ -1,16 +1,89 @@
# 0004 — Rich-text editor library and storage format
Date: 2026-07-03
Status: proposed
Status: accepted (2026-07-04)
## Context
Journal (task 86) and notes overhaul (task 85) need a shared rich-text editor with emoji support. Research and choose an existing library (e.g. TipTap/ProseMirror-family) rather than building one. Gitea: [#8](https://gitea.ginnoir.com/ginnoir/famapp/issues/8) (epic [#7](https://gitea.ginnoir.com/ginnoir/famapp/issues/7)).
Notes (task 85) and journal (task 86) need a shared rich-text editor with emoji and modern formatting. Notes today use a plain `<textarea>` and a minimal custom markdown preview (`#` headings, `-` lists only). Body is stored as plain text in `notes.body`. Mobile horizontal overflow is a known bug (Gitea #5). Gitea epic [#7](https://gitea.ginnoir.com/ginnoir/famapp/issues/7).
## What we decided (2026-07-04)
| # | Topic | Choice |
| --- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1 | Editor library | **TipTap** (ProseMirror family) — WYSIWYG, not home-grown |
| 2 | Storage format | **HTML** in existing `notes.body` text column; sanitize on every render |
| 3 | v1 formatting | **Full-featured**: headings, bold/italic, bullet + numbered lists, links, emoji, blockquotes, code blocks, tables, checklists, images, file attachments, undo/redo |
| 4 | Images & attachments | **Upload to famapp** via existing `/api/uploads`; embed returned URLs in note HTML |
| 5 | Shared component location | **`src/components/rich-text/`** — imported by notes now, journal later |
| 6 | Existing plain-text notes | **Lazy migration**: on read, non-HTML bodies wrapped as paragraphs; persisted as HTML on next save. No schema change. |
| 7 | Checklists on read surfaces | **Interactive everywhere** — index, dashboard widget, and share view can toggle checklist items (updates note via API) |
## Decision
To be filled when research completes.
### Editor: TipTap
- Use TipTap with React bindings and official extensions for the v1 feature set.
- Export contract for the shared component:
- `RichTextEditor` — controlled `value` / `onChange` (HTML string), optional `editable`, toolbar config
- `RichTextContent` — read-only renderer with sanitization and overflow-safe CSS
- `RichTextChecklist` behavior — interactive task items on read surfaces (see below)
- Journal (task 86) imports from `src/components/rich-text/` without forking.
### Storage: HTML in `notes.body`
- `notes.body` remains a `text` column; content is HTML after first save from the new editor.
- **Sanitize** all HTML before `dangerouslySetInnerHTML` (DOMPurify or equivalent) on every read surface: editor preview, index cards, dashboard widget, share viewer.
- Allowlist tags/attributes matching TipTap output (headings, lists, task lists, tables, links, images, code, blockquote, etc.).
- API `/api/v1/notes` continues to accept/return `body` as a string (now HTML). Document in OpenAPI.
### Lazy migration for legacy notes
- Helper `normalizeNoteBody(body: string): string` — if body does not start with `<` (heuristic) or fails HTML detection, convert plain text to `<p>` paragraphs (preserve line breaks).
- Run on read paths (editor load, index excerpt, widget, share) until user saves, which persists HTML.
- No one-shot migration script required for v1.
### Images and attachments
- TipTap image + file extensions call existing upload route (`POST /api/uploads`).
- Stored URLs embedded in HTML (`<img src="…">`, attachment links as `<a href="…">`).
- Upload auth: session or household bearer token (same as other API calls).
### Interactive checklists (read surfaces)
- TipTap TaskList extension for authoring.
- On index, widget, and share surfaces: render checklists with tappable checkboxes.
- Toggle calls a server action or `PATCH /api/v1/notes/:id` that updates only the task-item `checked` state inside the HTML (parse → flip `data-checked` / class → save). Activity log optional.
- Share links with write capability may allow toggle if share token grants write (reuse existing share write path where applicable).
### Mobile overflow
- Fix as part of `RichTextContent`: `overflow-wrap: anywhere`, `max-width: 100%`, table horizontal scroll container, no fixed-width embeds. Closes Gitea #5.
## Consequences
To be filled when research completes.
### Positive
- One editor stack for notes + journal; wife-friendly WYSIWYG.
- HTML storage is simple to render and matches TipTap's native export.
- Lazy migration avoids a risky bulk conversion; existing notes keep working.
- Upload reuse avoids new storage infrastructure.
### Trade-offs
- Full TipTap extension set increases bundle size — acceptable for an editor route; lazy-load editor chunk on notes/journal pages.
- HTML in DB is less readable than markdown for debugging; mitigated by Drizzle Studio and API still returning strings.
- Interactive checklists on read surfaces require HTML-aware toggle logic (not trivial CRUD) — budget extra implementation time in task 85.
- Sanitization is security-critical; must test XSS vectors and keep allowlist in sync with TipTap extensions.
### Out of scope (ADR)
- Journal module UI (task 86) — consumes shared component after 85 lands.
- Collaborative editing, comments, version history.
## References
- Task brief: `docs/tasks/85-rich-text-notes.md`
- Gitea: [#7](https://gitea.ginnoir.com/ginnoir/famapp/issues/7) epic, [#8](https://gitea.ginnoir.com/ginnoir/famapp/issues/8) research child
- Current notes editor: `src/modules/notes/components/note-editor.tsx`
- Uploads: `src/app/api/uploads/route.ts`
+1 -1
View File
@@ -26,7 +26,7 @@ What this costs us, what it buys us.
- [0002 — List realtime uses Postgres NOTIFY and SSE](0002-list-sse-notify.md)
- [0003 — Release workflow (commitlint + release-it)](0003-release-workflow.md)
- [0004 — Rich-text editor library and storage format](0004-rich-text-editor.md) (proposed)
- [0004 — Rich-text editor library and storage format](0004-rich-text-editor.md) (accepted 2026-07-04)
- [0005 — Journal / mood tracking research](0005-journal-research.md) (proposed)
- [0006 — API auth and LLM agent architecture](0006-api-llm-agent.md) (accepted 2026-07-04)
<!-- END AUTO-GENERATED -->