Estimate: 18m · Depends on: 2.3.5, 2.3.7
Yue feedback (finding #53): the description editor is a side-by-side
source + preview split — you type raw Markdown (**bold**) on
the left and watch a rendered pane on the right. That's wrong for the product: the user
should edit the rendered view directly and see formatting appear inline as
they type (the Linear / Notion / GitHub-comment experience), with NO raw-syntax pane.
This supersedes 2.3.5's "source editor, not WYSIWYG" decision. 2.3.5
chose @uiw/react-md-editor and explicitly rejected WYSIWYG on the grounds that
rich editors store ProseMirror JSON and serialize-to-Markdown lossily — which would break
Story 1.4's hard invariant (source of truth = Markdown text in
descriptionMd). The resolution that satisfies BOTH the UX ask and the
invariant is a remark-native WYSIWYG editor — one whose canonical document
model IS Markdown (parsed/serialized by remark), so editing the rendered view round-trips
to Markdown losslessly. Recommended: Milkdown (ProseMirror +
remark, purpose-built "WYSIWYG markdown" — Markdown is the document model, not a lossy
export); acceptable alternative: Tiptap + tiptap-markdown (more
popular, but Markdown is a serialization layer over PM JSON — accept ONLY if a round-trip
test proves no drift on our content set). The executor confirms the library at build time
against the round-trip-fidelity criterion; the storage shape does not change.
What stays fixed (the seams 2.3.5/2.3.6 already established):
descriptionMd — no schema
change, no new column, no JSON blob. The editor's onChange still emits a
Markdown string.MarkdownEditor prop contract is
UNCHANGED — value / onChange / label /
size: 'min' | 'full' / onImageUpload? / readOnly? —
so the two consumers (2.3.3 create modal, 2.3.6 edit form) need ZERO changes. This Subtask
swaps the editor INTERNALS, not the interface.MarkdownView / renderMarkdown
(the ONE remark+rehype-sanitize+highlight module) stays the display surface everywhere
else (detail page 2.4, list previews). The editor's inline rendering is sanitized too (no
raw-HTML injection through the WYSIWYG).onImageUpload
(placeholder→URL, polite notice on absent/failed — never silent-drop); theme via the
1.0.5 ThemeProvider (useOptionalTheme / dark mode); SSR-safe via
next/dynamic({ ssr: false }); readOnly renders the content with
no editing affordances.Size variants reinterpreted: min (create modal) = a compact
inline-edit surface (toolbar minimal or a selection bubble-menu, ~6 lines); full
(edit form) = the full inline-edit surface with a formatting toolbar (~16+ lines). Neither
shows a raw-source pane. @uiw/react-md-editor is removed from
package.json once no code imports it.
Full-width editing surface (Yue feedback). The full editor
must use the available screen width for a comfortable writing canvas — the edit-form route
(2.3.6) currently constrains content to a narrow max-w-[42rem] column, which
cramps the editor. Widen the edit surface: the editor (and the title/description editing
region of the edit form) should span a wide content container (e.g. max-w-[64rem]
/ the page's comfortable reading-plus-editing width, responsive — full width on smaller
viewports), and the editor itself is width: 100% of that container. The narrow
column was fine for a read view; an EDIT canvas wants room. (The min editor in
the create modal stays bounded by the modal width — the width ask is about the full editor.)
Post-build (Yue feedback): (1) E2E create→edit round-trip now types PLAIN PROSE — a WYSIWYG stores typed syntax as literal text + serializes it back escaped, so raw-Markdown-in-textarea round-trip is N/A; fidelity stays gated by the headless round-trip unit test. (2) Edit form widened to full content width (dropped the max-w-64rem cap; metadata grid → lg:grid-cols-3). (3) Removed the duplicate "Description" label at both the edit form + create modal (the editor owns its own label/aria-label).
Library shipped: Tiptap v3 + tiptap-markdown (over the card's Milkdown rec; round-trip-fidelity gate green). Scope trim: tables out of v1 (lossy round-trip).
**bold**-style content shows bold inline (or via toolbar/shortcut), with no separate raw-syntax pane and no side-by-side preview.full editor uses the available screen width — the edit-form route/page widens from the narrow max-w-[42rem] column to a wide editing container (e.g. max-w-[64rem], responsive: full width on smaller viewports), and the editor is width: 100% of it. Verified at desktop + a narrow breakpoint (no horizontal overflow). The min create-modal editor stays bounded by the modal.descriptionMd stays Markdown text (no schema/column change); onChange emits Markdown. A round-trip test proves a representative document (headings, bold/italic, links, lists, task lists, code block, table) survives load → render → edit-noop → serialize with NO drift (this is the gate that justifies the chosen library).MarkdownEditor public props are byte-identical to 2.3.5; 2.3.3's create modal is unchanged and 2.3.6's edit form changes ONLY its layout-container width (the full-width canvas above) — no prop/logic edits to either consumer. Existing 2.3.3/2.3.5/2.3.6 component tests pass or are updated only where they asserted the OLD split-pane internals or the old narrow width.readOnly shows content with no editing affordances.@uiw/react-md-editor removed from package.json once unused; the single-render-module grep guard (2.3.5) still holds (only lib/markdown/render.tsx imports react-markdown)./tokens/markdown-editor specimen + the STRICT shell-a11y sweep stay green: editor is keyboard-operable, the surface is labelled (the label prop), toolbar/bubble buttons carry accessible names; third-party editor chrome may keep the documented narrow exclusions (color-contrast / per-svg titles) as in 2.3.5.components/ui/MarkdownEditor.tsx (2.3.5) — the prop surface to PRESERVE + the image-paste/theme/SSR behaviour to carry over; editorConfigFor is replacedcomponents/ui/MarkdownView.tsx + lib/markdown/render.tsx — the read path + the sanitize/highlight chain to keep as display SoTdescriptionMd = Markdown source) + the AI-storage boundary note — the constraint that picks a remark-native editorCreateIssueModal + 2.3.6 EditIssueForm — the two consumers that must NOT need changesMilkdown (recommended) / Tiptap+tiptap-markdown docs — confirm SSR + dark-mode + image hooks + Markdown round-trip fidelity before committing