feat(composer): live markdown highlight overlay (LC-399) #414

Merged
longjacksonle merged 1 commit from feat/lc-399-composer-live-markdown-overlay into main 2026-06-20 22:41:28 +02:00

What

Live markdown highlight overlay for the chat composer (LC-399). Formatting now renders as you type (Discord-style) instead of showing literal Markdown that only resolves after sending or via the manual Write/Preview toggle.

How

Keeps the server-rendered, plain-textarea model rather than swapping in a contenteditable WYSIWYG (which would have meant rebuilding ~900 lines of textarea-bound JS: mention/emoji/channel/slash autocompletes, draft autosave, optimistic echo, voice recorder, caret restore, edit-last).

A .lc-mdback backdrop div behind the <textarea name="body"> mirrors the text with Markdown tokens styled (bold, italic, strike, code, headings, blockquotes, lists, links). The marker characters (**, `, >, -, #, []()) stay visible but dimmed. The textarea sits on top with transparent text/background and a visible caret, so it remains the single source of truth: every existing composer handler still reads and writes textarea[name=body] unchanged.

Key invariant: the highlighter only WRAPS characters in spans (never adds, removes, or substitutes any) and styles with colour / weight / decoration / background only, never font-size or font-family. So the backdrop wraps identically to the textarea and the caret never drifts. Any line the tokenizer does not recognise falls through as plain dimmed text (today's behavior), so it can never corrupt the submitted value, which is the textarea's, untouched.

  • Inline code spans are placeholder-protected with a U+E000 sentinel so emphasis never styles inside them and the restore step cannot collide with real text like a bare " 5 ".
  • Metrics on .lc-mdback and .lc-mdinput are pinned identical in CSS.
  • A :has(> .lc-mdinput.hidden) rule collapses the overlay when the LC-276 preview pane is shown, so the pane (its flex sibling) takes the slot.
  • Used by both rooms and DMs (shared room/composer.html).

Scope

Frontend only: server/templates/room/composer.html (wrapper + highlighter IIFE) and server/assets/main.css (overlay + token styles). No Rust change. The server-side pulldown-cmark renderer remains the source of truth for posted messages and the Preview pane; the overlay is decorative.

Tests

just check (standalone + saas compile, clippy, fmt), just test, and just test-saas all pass. just build-css regenerated.

## What Live markdown highlight overlay for the chat composer (LC-399). Formatting now renders as you type (Discord-style) instead of showing literal Markdown that only resolves after sending or via the manual Write/Preview toggle. ## How Keeps the server-rendered, plain-textarea model rather than swapping in a contenteditable WYSIWYG (which would have meant rebuilding ~900 lines of textarea-bound JS: mention/emoji/channel/slash autocompletes, draft autosave, optimistic echo, voice recorder, caret restore, edit-last). A `.lc-mdback` backdrop div behind the `<textarea name="body">` mirrors the text with Markdown tokens styled (bold, italic, ~~strike~~, `code`, headings, blockquotes, lists, links). The marker characters (`**`, `` ` ``, `>`, `-`, `#`, `[]()`) stay visible but dimmed. The textarea sits on top with transparent text/background and a visible caret, so it remains the single source of truth: every existing composer handler still reads and writes `textarea[name=body]` unchanged. Key invariant: the highlighter only WRAPS characters in spans (never adds, removes, or substitutes any) and styles with colour / weight / decoration / background only, never font-size or font-family. So the backdrop wraps identically to the textarea and the caret never drifts. Any line the tokenizer does not recognise falls through as plain dimmed text (today's behavior), so it can never corrupt the submitted value, which is the textarea's, untouched. - Inline code spans are placeholder-protected with a U+E000 sentinel so emphasis never styles inside them and the restore step cannot collide with real text like a bare " 5 ". - Metrics on `.lc-mdback` and `.lc-mdinput` are pinned identical in CSS. - A `:has(> .lc-mdinput.hidden)` rule collapses the overlay when the LC-276 preview pane is shown, so the pane (its flex sibling) takes the slot. - Used by both rooms and DMs (shared `room/composer.html`). ## Scope Frontend only: `server/templates/room/composer.html` (wrapper + highlighter IIFE) and `server/assets/main.css` (overlay + token styles). No Rust change. The server-side pulldown-cmark renderer remains the source of truth for posted messages and the Preview pane; the overlay is decorative. ## Tests `just check` (standalone + saas compile, clippy, fmt), `just test`, and `just test-saas` all pass. `just build-css` regenerated.
feat(composer): live markdown highlight overlay (LC-399)
All checks were successful
check-secrets / TruffleHog (push) Successful in 4s
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / Kingfisher (push) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 3s
check-secrets / Kingfisher (pull_request) Successful in 4s
check-secrets / Nosey parker (pull_request) Successful in 5s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 3m36s
f5e7eae8bc
The composer was a plain textarea showing literal Markdown; formatting only appeared after sending or via the manual Write/Preview toggle. This renders formatting live as the user types, Discord-style, without abandoning the server-rendered plain-textarea model.

A .lc-mdback backdrop div behind the textarea mirrors the text with Markdown tokens styled (bold/italic/strike/code/heading/blockquote/list/link), the marker characters kept but dimmed. The textarea sits on top with transparent text/background and a visible caret, so it stays the single source of truth: every existing composer handler (mention/emoji/channel/slash autocompletes, draft autosave, optimistic echo, voice recorder, caret restore, edit-last) keeps reading and writing textarea[name=body] unchanged.

The highlighter only wraps characters in spans (never adds, removes, or substitutes any) and styles with colour/weight/decoration/background only, never font-size or font-family, so the backdrop wraps identically to the textarea and the caret never drifts. Any line it does not recognise falls through as plain dimmed text (today's behavior), so it can never corrupt the submitted value. Inline code spans are placeholder-protected with a U+E000 sentinel so emphasis never styles inside them and the restore cannot collide with real text. Metrics on .lc-mdback and .lc-mdinput are pinned identical in CSS; a :has() rule collapses the overlay when the LC-276 preview pane is shown.

Frontend only (template + CSS), no Rust change. just check, just test, and just test-saas all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/lc-399-composer-live-markdown-overlay 2026-06-20 22:41:28 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!414
No description provided.