Enh: CodeMirror 6 code editor (HTML/JS/CSS, lazy-loaded) for HtmlBlock + Head Code #16

Merged
jknapp merged 1 commits from enh-code into main 2026-07-14 13:47:46 +00:00
Owner

Notepad/code editor with syntax highlighting + tag/attr autocomplete for HTML/JS/CSS via CodeMirror 6, lazy-loaded (main bundle +5.5KB; library in split chunks) with a textarea fallback. HtmlBlock gets an Edit HTML button; HeadCodeModal swaps its textarea for the editor. HtmlBlock purify unchanged. 665 tests.

🤖 Generated with Claude Code

Notepad/code editor with syntax highlighting + tag/attr autocomplete for HTML/JS/CSS via CodeMirror 6, lazy-loaded (main bundle +5.5KB; library in split chunks) with a textarea fallback. HtmlBlock gets an Edit HTML button; HeadCodeModal swaps its textarea for the editor. HtmlBlock purify unchanged. 665 tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 1 commit 2026-07-14 13:47:46 +00:00
Replaces the plain single-line input (HtmlBlock's `code` prop, via
GenericPropsEditor) and the plain <textarea> (HeadCodeModal's site-wide
head code) with a proper syntax-highlighted, tab-completing code editor.

- New src/ui/CodeEditor.tsx: reusable CodeMirror 6 editor (state/view/
  commands/autocomplete/language/lang-html/lang-css/lang-javascript/
  theme-one-dark). All @codemirror/* packages are pulled in via a single
  dynamic import() inside the component so they land in separate lazy
  chunks instead of the main bundle -- confirmed via `npm run build`:
  main editor.js grew by only ~5.5KB (657KB -> 663KB raw) while ~570KB of
  CodeMirror source split into index*.js chunks that only load when a
  code editor modal is actually opened. While that import is in flight,
  or if it ever fails, the component renders a plain <textarea> so typing
  never breaks.
- GenericPropsEditor.tsx: special-cases the `code` prop (used only by
  HtmlBlock today) into an "Edit HTML" button that opens the CodeEditor
  in a modal (language="html"), instead of rendering it as a single-line
  text input alongside the component's other string props.
- HeadCodeModal.tsx: swaps its <textarea> for CodeEditor (language="html"
  -- head code is HTML with embedded <script>/<style>), keeping the
  existing SiteDesignContext.updateDesign({ headCode }) wiring.

GuidedStyles.tsx untouched: HtmlBlock's displayName ("HTML") already
matches GuidedStyles' `isUtility` regex and routes to GenericPropsEditor,
so no dispatcher change was needed.

HtmlBlock.tsx untouched: purifyHtml/toHtml sanitization is unchanged, as
specified. Skipped the optional AnimationControl/VisibilityControl
addition -- HtmlBlock has no dedicated StylePanel (it shares
GenericPropsEditor with Divider/Spacer/every unmatched type), and doing
it well would mean adding real Animation/Visibility widgets to that
shared editor for every consumer, which is a bigger change than "trivial"
for this package's scope.

Tests: CodeEditor.test.tsx and HeadCodeModal.test.tsx both exploit the
fact that dynamic import() always resolves on a later microtask, so
asserting against the DOM immediately after the initial synchronous
render deterministically exercises the <textarea> fallback path (value
display, onChange wiring, language prop plumbing) without needing to
mock @codemirror/*.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jknapp merged commit d07bc7789d into main 2026-07-14 13:47:46 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cloud-hosting-platform/site-builder#16