CodeMirror 6 Migration Plan
Objective
Migrate the current custom Markdown editor to CodeMirror 6 to:
- recover robust Markdown syntax highlighting
- improve performance on medium and large documents
- simplify editor behavior and state management
- make preview synchronization consistent across:
- Teams
- Jira Visual
- Jira Text
- HTML Preview
- HTML Source
- stop layout/render regressions caused by the custom textarea + overlay approach
This migration must preserve:
- static deployment
- browser-only execution
- local-first behavior
- PWA/offline support
- current product model: one Markdown source, multiple output previews
Scope
In scope
- add a minimal build step
- integrate CodeMirror 6
- replace the current textarea editor
- migrate toolbar behavior
- migrate slash commands
- rebuild preview synchronization
- add performance gating for large documents
- preserve autosave, import/export, copy, preview tabs, PWA
Out of scope
- Monaco migration
- backend/cloud features
- new output formats
- advanced linting/formatting
- collaborative editing
- large redesign of UI
Current Problems To Solve
Editor architecture
- custom textarea editor depends on:
- manual overlay syntax highlight
- manual line numbers
- manual selection editing
- manual scroll sync
- manual slash menu positioning
- this is fragile and expensive to maintain
Preview sync
- Teams works better now, but Jira/HTML remain inconsistent
- source/text outputs do not have a robust line/block mapping model
- tall blocks like tables/images still stress synchronization logic
Performance
- input handling currently fans out into too much work:
- stats
- line numbers
- highlight overlay
- preview rendering
- scroll sync
- large files degrade typing performance
Layout/render stability
- Jira/HTML preview shells have already regressed in height/stretch behavior
- iframe-based previews need persistence to avoid blink and DOM churn
Proposed Architecture
1. Keep converter-core.js as pure rendering logic
Responsibilities:
- Markdown parsing
- Markdown -> Teams HTML
- Markdown -> Jira text
- Markdown -> Jira visual HTML
- Markdown -> HTML preview/source artifact
- block metadata generation:
startLineendLine
Expected outcome:
- output renderers remain testable
- source mapping becomes explicit instead of inferred in app code
2. Introduce editor-cm.js
New file responsible for CodeMirror integration.
Responsibilities:
- create editor instance
- expose editor API to app layer
- configure:
- Markdown language
- syntax highlighting
- line numbers
- history
- selection behavior
- theme
- provide cursor/viewport information
Suggested public API:
createMarkdownEditor(root, options)getValue()setValue(value)focus()dispatch(spec)replaceSelection(text)getCursorLine()getSelection()getVisibleLineRange()scrollToLine(line)onChange(callback)
3. Introduce preview-sync.js
New file responsible for all editor/preview synchronization.
Responsibilities:
- map editor cursor line to preview block
- map editor viewport to preview viewport
- support:
- Teams visual
- Jira visual
- Jira text
- HTML preview
- HTML source
Expected outcome:
- one synchronization model
- fewer per-format hacks inside
converter-app.js
4. Reduce converter-app.js to orchestration
Responsibilities after migration:
- app state
- view mode
- preview tab selection
- file import/export
- autosave
- toolbar wiring
- slash command orchestration
- calling editor API
- calling preview API
Expected outcome:
- less editor-specific DOM code
- simpler update cycle
- easier maintenance
Technical Decisions
Decision 1. Use CodeMirror 6
Recommended: yes
Reason:
- lighter than Monaco
- better suited for static browser app
- enough performance and flexibility
- easier incremental migration
Decision 2. Add build step
Recommended: yes
Tool:
- Vite
Reason:
- easiest path to bundle CodeMirror
- keeps app static
- works well with GitHub Pages and PWA assets
Decision 3. Preview render policy
Recommended:
Split: render preview livePreview: render preview liveEditor: do not render preview while typing- large docs: auto-open in
Editormode when loaded
Reason:
- best performance win with minimal product compromise
Dependency Plan
Initial packages to add:
vite@codemirror/state@codemirror/view@codemirror/commands@codemirror/language@codemirror/lang-markdown@codemirror/search@codemirror/autocomplete@lezer/highlight
Optional depending on exact setup:
@codemirror/gutter@codemirror/history@codemirror/theme-one-dark
Do not add extra editor packages until the base migration is stable.
Phase 1. Build Pipeline
Goal
Introduce a minimal bundle step without changing behavior.
Tasks
- Add
vitetopackage.json - Create minimal Vite config targeting
docs/markdown - Define entrypoint for app bundle
- Keep
converter.htmlas the published app shell - Update asset references if needed
- Ensure service worker still caches built assets
- Add build script(s):
build- optional
dev
Deliverables
- Vite config
- updated scripts
- working static build output
Validation
- app still opens locally
- app still deploys statically
- PWA still works
- no functional regressions
Phase 2. Mount CodeMirror 6
Goal
Replace the core editor engine while preserving current UI.
Tasks
- Create
editor-cm.js - Add editor mount container in existing editor panel
- Initialize CM6 with:
- markdown language
- dark theme
- line numbers
- history
- Bind editor content to existing app state
- Preserve autosave/localStorage restore
- Preserve focus behavior on load
- Keep current toolbar visually unchanged for now
Deliverables
- working CM6 editor inside current editor shell
- syntax highlighting restored
- line numbers restored
Validation
- typing works
- undo/redo works
- syntax highlighting visible
- line numbers visible
- no custom overlay needed
Phase 3. Replace Textarea-Specific Logic
Goal
Remove dependence on textarea APIs.
Tasks
- Replace all reads of:
editor.valueselectionStartselectionEndscrollTop
- Replace with CM6 equivalents
- Update:
- stats calculation source
- doc info source
- autosave source
- Keep DOM ids stable where practical for tests
Deliverables
- app no longer depends on textarea internals
Validation
- no runtime use of textarea-only APIs remains in active code path
- tests still pass after adapter changes
Phase 4. Toolbar Migration
Goal
Move Markdown toolbar actions to CM6 transactions.
Tasks
- Reimplement:
- heading
- bold
- italic
- quote
- inline code
- link
- bulleted list
- numbered list
- task list
- code block
- Use CM6 transactions instead of string surgery on textarea
- Preserve selection where appropriate
- Preserve undo/redo granularity
Deliverables
- toolbar works against CM6 editor state
Validation
- toolbar actions behave correctly on:
- empty selection
- inline selection
- multiline selection
- undo/redo returns to previous state cleanly
Phase 5. Slash Command Migration
Goal
Replace manual slash positioning and state with CM6-aware behavior.
Tasks
- Keep current slash command list
- Use CM6 cursor position to detect trigger
- Use CM6 geometry to place menu
- Preserve:
- filtering
- arrow navigation
- enter/tab accept
- escape dismiss
- Ensure inserted snippets use CM6 transactions
Deliverables
- slash menu backed by CM6 state
Validation
- slash menu opens in the correct place
- snippets insert correctly
- menu navigation remains stable
Phase 6. Standardize Source Mapping In Preview Renderers
Goal
Make preview synchronization based on explicit metadata.
Tasks
- Ensure all visual preview outputs wrap blocks with:
data-src-startdata-src-end
- Apply to:
- Teams visual output
- Jira visual output
- HTML preview output
- Keep source/text outputs line-based
- Ensure
converter-core.jsis the source of truth for block mapping
Deliverables
- consistent source-map-ready preview DOM across visual outputs
Validation
- rendered DOM contains stable source mapping metadata
- mapping survives headings, tables, images, code blocks, lists, quotes
Phase 7. Rebuild Preview Synchronization
Goal
Make all output formats follow editing consistently.
Tasks
- Implement shared synchronization module in
preview-sync.js - Use editor cursor line and viewport information from CM6
- For visual outputs:
- find mapped block by line range
- center or keep active block comfortably visible
- For source/text outputs:
- use line-centered scrolling
- Keep iframe shells persistent for:
- Jira visual
- HTML preview
- Separate typing sync from scroll sync:
- typing -> active block
- scrolling -> viewport ratio/viewport anchor
Deliverables
- one synchronization model across formats
Validation
- Teams sync works
- Jira Visual sync works
- Jira Text sync works
- HTML Preview sync works
- HTML Source sync works
- tall tables/images do not break visible editing context badly
Phase 8. Performance Strategy
Goal
Reduce typing latency on medium/large files.
Tasks
- Render preview only when:
SplitPreview
- Skip preview rendering in
Editormode - Split update cycle into:
- immediate editor-state updates
- deferred preview rendering
- Use
requestAnimationFramefor preview refresh - Use caching for unchanged output per tab if possible
- Keep iframe documents persistent instead of recreating them
- Remove legacy overlay/highlight work entirely
- Define large-document thresholds:
- lines >= 600
- or chars >= 120000
- On large file load:
- default to
Editor - show small UI notice that preview is paused until enabled
- default to
Deliverables
- faster typing path
- stable large-document behavior
Validation
- large file editing feels responsive
- preview hidden means lower work
- split mode still works when explicitly enabled
Phase 9. Cleanup Legacy Editor Code
Goal
Finish the migration and remove obsolete systems.
Tasks
- Remove:
- textarea overlay highlight logic
- manual line-number sync
- performance-lite workaround for old textarea stack
- redundant scroll sync branches
- Simplify tests that depended on old DOM structure
- Keep the outer shell/UI stable
Deliverables
- smaller
converter-app.js - editor logic isolated
- preview sync logic isolated
Validation
- no dead code paths for old editor remain
- final architecture matches intended separation
Phase 10. PWA And Release Hardening
Goal
Make the migration safe for production use.
Tasks
- Update
sw.jscache name - Ensure bundled assets are cached
- Verify manifest still correct
- Run offline smoke test
- Verify no broken asset references
- Check GitHub Pages path compatibility
Deliverables
- production-ready static build
- working offline behavior
Validation
- reload works offline after first load
- no missing bundle files
- app boots correctly from published path
Testing Plan
Unit tests
Add or update tests for:
- block parsing with line ranges
- Teams render mapping
- Jira visual mapping
- HTML preview mapping
- toolbar transformation logic if isolated
E2E tests
Add or update Playwright coverage for:
- editor loads with syntax highlighting
- toolbar actions work with undo/redo
- slash commands work
- Teams sync while typing
- Jira Visual sync while typing
- Jira Text sync while typing
- HTML Preview sync while typing
- HTML Source sync while typing
- Jira/HTML visual previews do not blink
- preview shells fill panel height
- large file opens in
Editormode Editormode skips preview work- opening ~1000-line file remains responsive enough
Success Criteria
The migration is successful when:
- Markdown syntax highlighting is restored via CodeMirror
- typing is smoother on medium/large docs
- Teams/Jira/HTML sync consistently
- Jira/HTML previews stop blinking
- preview panels use full height correctly
Editormode is the fastest path- toolbar and slash commands still behave correctly
- undo/redo remains robust
- app remains static, local-first, and PWA-compatible
Implementation Order
- Build pipeline
- CodeMirror mount
- textarea API replacement
- toolbar migration
- slash menu migration
- source mapping standardization
- cross-format preview sync
- performance gating
- cleanup
- PWA hardening
Notes
- Do not attempt Monaco first.
- Do not keep both old editor and CM6 long-term.
- Do not continue growing the current custom overlay editor.
- Prefer small, phased PRs/changes over a giant rewrite.