- Shipped
- August 24, 2026 at 9:27 PM UTC
- Author
- kamo
- Commit
- e73518e
The description on **************** was a three-row textarea. It is the /kb/new writing surface now, minus pictures and the media library — those upload into KB media, which is the right home for an article and the wrong one for a settings field. The column holds two shapes from here on: a Lexical document on anything saved since, and bare prose on every row older than that, since nothing rewrites them. app/lib/richTextField.ts is the bridge. It detects a document by parsing rather than by a leading brace — prose like '{beta} customers only' is legal, and sniffing would send it through parseEditorState, which throws, opens the editor empty and loses the author's text on the next save. Legacy prose is wrapped into a document so it opens as editable text instead. An emptied editor still serializes to one blank paragraph, so what gets stored is decided by the plain text the editor hands back, not by the JSON — a cleared description reads as absent everywhere downstream. Every reader is fed the words rather than the document: the booking wizard's picker card keeps its two-line teaser, and the confirm step gains the rendered description, which is the one place the formatting an author wrote is actually read. RichTextView renders it through the sanitising lexicalToHtml, and shows a legacy value as escaped text — whatever a paste once carried into a plain-text field is exactly what would otherwise run. KbArticleEditor gains placeholder and allowMedia; the note preview typography in globals.css is now shared with a .kamo-richtext reading surface, which differs only in that its links work. Requires the MediaService that knows descriptionPlain — deployed and verified before this landed, since its ObjectMapper rejects unknown properties.