Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
28e6006
test(mdviewer): add document cache and file switching integration tests
abose Mar 30, 2026
63b8696
test(mdviewer): add LRU cache test for files removed from working set
abose Mar 30, 2026
7d530a8
test(mdviewer): add selection sync tests, fix stale CM reference in M…
abose Mar 30, 2026
a781f69
test(mdviewer): add toolbar & UI tests, fix _getCM stale reference
abose Mar 30, 2026
6a68ca1
test(mdviewer): add format bar, link sync, empty line, and slash menu…
abose Mar 30, 2026
be410ef
test(mdviewer): improve test reliability and cleanup unused helpers
abose Mar 31, 2026
3e11211
test(mdviewer): verify CM source after link popover edit and remove
abose Mar 31, 2026
f3d0bfa
refactor(tests): replace _codeMirror access with Editor/Document APIs
abose Mar 31, 2026
70afe37
test(mdviewer): add tests for link click opening in default browser
abose Mar 31, 2026
b342173
test(mdviewer): add cursor/scroll sync tests, use real DOM interactions
abose Mar 31, 2026
15081f6
docs: split md viewer test guide into CLAUDE-markdown-viewer.md
abose Mar 31, 2026
17eb6f1
fix(mdviewer): restore cursor position on Escape in link popover
abose Mar 31, 2026
b270054
chore(mdviewer): update test checklist for entitlement gating coverage
abose Mar 31, 2026
3887446
build: update pro deps
abose Mar 31, 2026
477cc57
test(mdviewer): add checkbox task list sync integration tests
abose Mar 31, 2026
0aef3ff
test(mdviewer): add code block editing and checkbox integration tests
abose Mar 31, 2026
7345b97
test(mdviewer): add list editing integration tests
abose Mar 31, 2026
e3fa543
docs: update test completions
abose Mar 31, 2026
7585ae2
test(mdviewer): improve list split and Shift+Enter assertions
abose Mar 31, 2026
8a0840d
test(mdviewer): add UL/OL toggle tests, fix list type switch sync
abose Mar 31, 2026
2fbc72a
refactor(tests): use beforeAll/beforeEach for UL/OL toggle tests
abose Mar 31, 2026
7b7db85
build: update pro deps
abose Mar 31, 2026
41e070c
test(mdviewer): add heading editing integration tests
abose Mar 31, 2026
3a1ca7c
test(mdviewer): add in-document search (Ctrl+F) integration tests
abose Mar 31, 2026
a25b599
test(mdviewer): add in-document search (Ctrl+F) integration tests
abose Mar 31, 2026
2bd5c32
fix(tests): add cache reset and HTML→MD transitions for test isolation
abose Mar 31, 2026
6537196
test(mdviewer): add table editing integration tests
abose Mar 31, 2026
d006b99
fix(tests): fix flaky CI failures in md viewer tests
abose Mar 31, 2026
836291a
feat(mdviewer): sync toolbar state from CM cursor position
abose Mar 31, 2026
2d2b9b7
feat(mdviewer): add image popover with edit/delete and keyboard nav
abose Mar 31, 2026
0e3d439
feat(mdviewer): add upload button to Edit Image URL dialog
abose Mar 31, 2026
c29399e
feat(mdviewer): add persistent bidirectional cursor sync highlights
abose Mar 31, 2026
0d62b87
fix(mdviewer): extract CM event handlers and use off→on pattern
abose Mar 31, 2026
353a94a
fix(mdviewer): prevent cursor sync highlight flash during CM typing
abose Mar 31, 2026
bbc91a4
feat(mdviewer): add drag auto-scroll near edges in edit mode
abose Mar 31, 2026
a56e049
feat(mdviewer): add bidirectional synchronized scrolling
abose Mar 31, 2026
d5c334a
feat(mdviewer): add fine-grained data-source-line to nested elements
abose Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ Use `exec_js` to run JS in the Phoenix browser runtime. jQuery `$()` is global.

**Check logs:** `get_browser_console_logs` with `filter` regex (e.g. `"AI UI"`, `"error"`) and `tail` — includes both browser console and Node.js (PhNode) logs. Use `get_terminal_logs` for Electron process output (only available if Phoenix was launched via `start_phoenix`).

## Writing Tests
- **Never use `awaits(number)`** (fixed-time waits) in tests — they cause flaky failures. Always use `awaitsFor(condition)` to wait for a specific condition to become true.
- Use `editor.*` APIs (e.g. `editor.document.getText()`, `editor.getCursorPos()`, `editor.setSelection()`) instead of accessing `editor._codeMirror` directly.
- Tests should be independent — no shared mutable state between `it()` blocks. Use `FILE_CLOSE` with `{ _forceClose: true }` to clean up.
- For markdown viewer/live preview architecture, test patterns, and debugging — see `src-mdviewer/CLAUDE-markdown-viewer.md`.

## Running Tests via MCP

The test runner must be open as a separate Phoenix instance (it shows up as `phoenix-test-runner-*` in `get_phoenix_status`). Use `run_tests` to trigger test runs and `get_test_results` to poll for results. `take_screenshot` also works on the test runner.
Expand Down
98 changes: 98 additions & 0 deletions src-mdviewer/CLAUDE-markdown-viewer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Markdown Viewer/Editor — Development & Testing Guide

## Architecture

The markdown viewer (`src-mdviewer/`) is a standalone web app loaded inside an iframe in Phoenix's Live Preview panel. It communicates with Phoenix via postMessage.

### Iframe nesting (in tests)
```
Test Runner Window
└── Test Phoenix iframe (testWindow)
├── CM5 editor (CodeMirror)
├── Live Preview panel
│ └── #panel-md-preview-frame (md viewer iframe)
│ ├── #viewer-content (contenteditable in edit mode)
│ ├── #format-bar, #link-popover
│ └── embedded-toolbar (reader/edit toggle, cursor sync btn)
└── MarkdownSync.js (listens for postMessage from md iframe)
```

### Key source files
- `src-mdviewer/src/bridge.js` — postMessage bridge between Phoenix and md iframe. Handles file switching, content sync, keyboard shortcuts, edit mode.
- `src-mdviewer/src/core/doc-cache.js` — Document DOM cache with LRU eviction for file switching.
- `src-mdviewer/src/components/editor.js` — Contenteditable WYSIWYG editing, Turndown HTML→Markdown conversion.
- `src-mdviewer/src/components/embedded-toolbar.js` — Reader/edit toggle, cursor sync, format buttons.
- `src-mdviewer/src/components/format-bar.js` — Floating format bar on text selection (bold, italic, underline, link).
- `src-mdviewer/src/components/link-popover.js` — Link popover for editing/removing links in edit mode.
- `src-mdviewer/src/components/viewer.js` — Reader mode click handling, link interception, copy buttons.
- `src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js` — Phoenix-side sync: CM↔iframe content, cursor, scroll, selection.

## Communication: postMessage (reliable in both directions)

- **Phoenix → iframe**: `iframe.contentWindow.postMessage({ type: "MDVIEWR_SET_EDIT_MODE", ... })` — mode switches, file content updates
- **iframe → Phoenix**: bridge.js calls `window.parent.postMessage({ type: "MDVIEWR_EVENT", eventName: "...", ... })` — keyboard shortcuts, content changes, cursor sync, link clicks
- MarkdownSync.js listens for these messages and acts on them (scroll CM, open URLs, handle undo/redo)

### Message types (Phoenix → iframe)
| Type | Purpose |
|------|---------|
| `MDVIEWR_SET_EDIT_MODE` | Toggle edit/reader mode |
| `MDVIEWR_SWITCH_FILE` | Switch to a new file with markdown content |
| `MDVIEWR_CONTENT_UPDATE` | Update content from CM edits |
| `MDVIEWR_SCROLL_TO_LINE` | Scroll viewer to a source line (cursor sync) |
| `MDVIEWR_HIGHLIGHT_SELECTION` | Highlight blocks corresponding to CM selection |

### Event names (iframe → Phoenix via `MDVIEWR_EVENT`)
| eventName | Purpose |
|-----------|---------|
| `mdviewrContentChanged` | Editor content changed (sync to CM) |
| `mdviewrEditModeChanged` | Edit/reader mode toggled |
| `mdviewrKeyboardShortcut` | Forwarded shortcut (Ctrl+S, Ctrl+Shift+F, etc.) |
| `mdviewrUndo` / `mdviewrRedo` | Undo/redo requests |
| `mdviewrScrollSync` | Scroll sync from edit mode click |
| `mdviewrSelectionSync` | Selection sync from viewer to CM |
| `mdviewrCursorSyncToggle` | Cursor sync button toggled |
| `embeddedIframeFocusEditor` | Reader mode click — refocus CM, scroll to source line |
| `embeddedIframeHrefClick` | Link click — opens URL via `NativeApp.openURLInDefaultBrowser` |
| `embeddedEscapeKeyPressed` | Escape key — refocus Phoenix editor |

## Integration Tests

Test file: `test/spec/md-editor-integ-test.js`
Category: `livepreview`, Suite: `livepreview:Markdown Editor`

### Accessing the md iframe from tests
The md iframe is **directly DOM-accessible** (no sandbox in test mode):
```js
testWindow.document.getElementById("panel-md-preview-frame") // iframe element
iframe.contentDocument // query #viewer-content, #format-bar, etc.
iframe.contentWindow // access __setEditModeForTest, __getCurrentContent, etc.
```

### Test helpers exposed on iframe window (`__` prefix)
- `win.__setEditModeForTest(bool)` — toggle edit/reader mode
- `win.__getCurrentContent()` — get the markdown source currently loaded in viewer
- `win.__getActiveFilePath()` — current file path in viewer
- `win.__isSuppressingContentChange()` — true during re-render (wait for false before asserting)
- `win.__triggerContentSync()` — force content sync after `execCommand` formatting
- `win.__getCacheKeys()` / `win.__getWorkingSetPaths()` — inspect doc cache state

### Key test patterns
- **Wait for sync**: `_waitForMdPreviewReady(editor)` — mandatory after every file switch. Verifies iframe visible, bridge initialized, content rendered, and `editor.document.getText()` matches `win.__getCurrentContent()`.
- **Formatting**: Use `_execCommandInMdIframe("bold")` — browsers reject `execCommand` from untrusted `KeyboardEvent`s, so synthetic key events don't work for formatting.
- **Keyboard shortcuts**: Use `_dispatchKeyInMdIframe(key)` — bridge.js captures these and forwards via postMessage to MarkdownSync.
- **Clicking elements**: Click directly on iframe DOM elements (e.g. `paragraph.click()`). The bridge.js click handler fires and sends the appropriate postMessage. Always test the real click flow.
- **Editor APIs**: Use `editor.document.getText()`, `editor.setCursorPos()`, `editor.setSelection()`, `editor.getSelectedText()`, `editor.replaceRange()`, `editor.lineCount()`, `editor.getLine()` — never access `editor._codeMirror` directly.

### Rules
- **Never use `awaits(number)`** — always use `awaitsFor(condition)`.
- **Tests must be independent** — no shared mutable state between `it()` blocks. Use `FILE_CLOSE` with `{ _forceClose: true }` to clean up.
- **Test real behavior** — use actual DOM clicks and CM API calls, not fabricated postMessages.
- **Negative assertions** — move state to a known position first, perform the action, then verify state didn't change.
- **Function interception** — save originals in `beforeAll`, restore in `afterAll` to guard against test failures.

### Debugging test failures
- **Stale DOM refs**: After toolbar re-render or file switch, re-query with `_getMdIFrameDoc().getElementById(...)`.
- **Dirty state**: Check if a prior test left cursor sync disabled, edit mode on, etc. Tests should clean up.
- **Fixture files**: Live in `test/spec/LiveDevelopment-Markdown-test-files/`. After modifying, run `npm run build` and reload the test runner.
- **Test checklist**: `src-mdviewer/to-create-tests.md` tracks what's covered and what's pending.
1 change: 1 addition & 0 deletions src-mdviewer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
</div>
<div class="format-bar" id="format-bar" role="toolbar"></div>
<div class="link-popover" id="link-popover" role="dialog"></div>
<div class="image-popover" id="image-popover" role="toolbar"></div>
<div class="context-menu" id="context-menu" role="menu"></div>
<div class="table-context-menu" id="table-context-menu" role="menu"></div>
<div class="slash-menu-anchor" id="slash-menu-anchor">
Expand Down
Loading
Loading