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.
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)
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 main2026-07-14 13:47:46 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
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>