@vscode/markdown-editor 0.0.2-14 → 0.0.2-140

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +663 -0
  2. package/dist/commands.d.ts +67 -0
  3. package/dist/commands.js +185 -0
  4. package/dist/commands.js.map +1 -0
  5. package/dist/config-BGeaJqWk.js +286 -0
  6. package/dist/config-BGeaJqWk.js.map +1 -0
  7. package/dist/config.d.ts +10 -0
  8. package/dist/config.js +6 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/index.d.ts +1615 -270
  11. package/dist/index.js +7845 -3291
  12. package/dist/index.js.map +1 -1
  13. package/dist/markdown-editor.css +1 -1
  14. package/dist/scrolling-C4GW1cCQ.js +391 -0
  15. package/dist/scrolling-C4GW1cCQ.js.map +1 -0
  16. package/dist/web-editors.d.ts +357 -0
  17. package/dist/web-editors.js +7597 -0
  18. package/dist/web-editors.js.map +1 -0
  19. package/package.json +60 -20
  20. package/src/contrib/comments/commentInput.css +38 -80
  21. package/src/contrib/comments/commentWidget.css +60 -0
  22. package/src/contrib/find/find.css +347 -0
  23. package/src/contrib/rename/rename.css +73 -0
  24. package/src/contrib/suggest/suggest.css +109 -0
  25. package/src/view/editor.css +1702 -111
  26. package/src/view/themes/default.css +21 -4
  27. package/src/view/themes/github.css +25 -13
  28. package/src/view/themes/vscode-default.css +98 -23
  29. package/src/view/themes/vscode-github.css +84 -15
  30. package/dist/_observables/assert.d.ts +0 -2
  31. package/dist/_observables/deps.d.ts +0 -28
  32. package/dist/_observables/disposables.d.ts +0 -19
  33. package/dist/_observables/equals.d.ts +0 -32
  34. package/dist/_observables/index.d.ts +0 -2
  35. package/dist/_observables/observableInternal/base.d.ts +0 -172
  36. package/dist/_observables/observableInternal/changeTracker.d.ts +0 -44
  37. package/dist/_observables/observableInternal/commonFacade/cancellation.d.ts +0 -24
  38. package/dist/_observables/observableInternal/commonFacade/deps.d.ts +0 -24
  39. package/dist/_observables/observableInternal/debugLocation.d.ts +0 -21
  40. package/dist/_observables/observableInternal/debugName.d.ts +0 -33
  41. package/dist/_observables/observableInternal/experimental/deferUnobserve.d.ts +0 -16
  42. package/dist/_observables/observableInternal/experimental/reducer.d.ts +0 -37
  43. package/dist/_observables/observableInternal/experimental/utils.d.ts +0 -15
  44. package/dist/_observables/observableInternal/index.d.ts +0 -25
  45. package/dist/_observables/observableInternal/logging/consoleObservableLogger.d.ts +0 -40
  46. package/dist/_observables/observableInternal/logging/debugGetDependencyGraph.d.ts +0 -4
  47. package/dist/_observables/observableInternal/logging/debugger/debuggerRpc.d.ts +0 -4
  48. package/dist/_observables/observableInternal/logging/debugger/devToolsLogger.d.ts +0 -43
  49. package/dist/_observables/observableInternal/logging/debugger/rpc.d.ts +0 -41
  50. package/dist/_observables/observableInternal/logging/debugger/utils.d.ts +0 -13
  51. package/dist/_observables/observableInternal/logging/logging.d.ts +0 -30
  52. package/dist/_observables/observableInternal/map.d.ts +0 -18
  53. package/dist/_observables/observableInternal/observables/baseObservable.d.ts +0 -58
  54. package/dist/_observables/observableInternal/observables/constObservable.d.ts +0 -5
  55. package/dist/_observables/observableInternal/observables/derived.d.ts +0 -46
  56. package/dist/_observables/observableInternal/observables/derivedImpl.d.ts +0 -83
  57. package/dist/_observables/observableInternal/observables/lazyObservableValue.d.ts +0 -27
  58. package/dist/_observables/observableInternal/observables/observableFromEvent.d.ts +0 -37
  59. package/dist/_observables/observableInternal/observables/observableSignal.d.ts +0 -11
  60. package/dist/_observables/observableInternal/observables/observableSignalFromEvent.d.ts +0 -5
  61. package/dist/_observables/observableInternal/observables/observableValue.d.ts +0 -37
  62. package/dist/_observables/observableInternal/observables/observableValueOpts.d.ts +0 -8
  63. package/dist/_observables/observableInternal/reactions/autorun.d.ts +0 -56
  64. package/dist/_observables/observableInternal/reactions/autorunImpl.d.ts +0 -53
  65. package/dist/_observables/observableInternal/set.d.ts +0 -17
  66. package/dist/_observables/observableInternal/transaction.d.ts +0 -27
  67. package/dist/_observables/observableInternal/utils/promise.d.ts +0 -75
  68. package/dist/_observables/observableInternal/utils/runOnChange.d.ts +0 -7
  69. package/dist/_observables/observableInternal/utils/utils.d.ts +0 -43
  70. package/dist/_observables/observableInternal/utils/utilsCancellation.d.ts +0 -10
  71. package/dist/_observables/observableInternal/utils/valueWithChangeEvent.d.ts +0 -10
  72. package/dist/_observables/utils.d.ts +0 -15
  73. package/dist/observables.d.ts +0 -4
  74. package/dist/observables.js +0 -439
  75. package/dist/observables.js.map +0 -1
  76. package/dist/runOnChange-C00UIwqQ.js +0 -1538
  77. package/dist/runOnChange-C00UIwqQ.js.map +0 -1
  78. package/src/contrib/comments/comments.css +0 -129
  79. package/src/contrib/commentsVscode/vscodeCommentWidgetV2.css +0 -166
package/README.md ADDED
@@ -0,0 +1,663 @@
1
+ # Markdown editor
2
+
3
+ `@vscode/markdown-editor` is an experimental, browser-based hybrid Markdown
4
+ editor. It keeps Markdown source as the canonical document while presenting
5
+ inactive blocks in rendered form and active blocks in editable source form.
6
+
7
+ ## Package releases
8
+
9
+ The npm release pipelines run `pnpm exec turbo build --filter=@vscode/markdown-editor`.
10
+ The package build depends on the workspace observables, HubRPC, and web-editors
11
+ builds. Fixture-only dependencies (Component Explorer and the cmark WASM compiler)
12
+ remain in the fixture/screenshot task graph, not the library release graph.
13
+ Both stable and nightly release jobs install Chromium before running the package
14
+ tests, which include real-browser clipboard and widget tests.
15
+
16
+ The Markdown test runner is temporarily pinned to Playwright 1.56.1, matching
17
+ Monaco's release tooling. Its Chromium download uses Microsoft's distribution
18
+ endpoint; newer Chrome-for-Testing downloads redirect to Google storage, which
19
+ failed with `EPERM` on the Azure release agent. Keep the exact pin until the newer
20
+ download route is approved and verified on that agent. This affects test tooling,
21
+ not the published editor's runtime dependencies or Component Explorer's renderer.
22
+
23
+ ## Embedded editor presentation
24
+
25
+ `EditorView` calls the embedded editor factory's optional `layout()` hook after
26
+ measuring the document. The virtualized iframe factory uses it to keep retained
27
+ iframes aligned when edits move their placeholders without resizing them.
28
+
29
+ Iframe editor descriptors can opt into host-owned presentation controls:
30
+
31
+ ```ts
32
+ presentation: {
33
+ resizable: true,
34
+ fullWidth: true,
35
+ fullView: true,
36
+ minHeight: 180,
37
+ }
38
+ ```
39
+
40
+ These controls work without an editor protocol implementation in the iframe.
41
+ Full width uses the Markdown viewport width while retaining document flow.
42
+ Full view fills the visible Markdown viewport, keeps the same iframe alive,
43
+ and restores the previous width mode, inline height, and scroll position on exit.
44
+ The host toolbar groups copy and presentation actions in a compact, themed Codicon
45
+ overlay. It appears on hover or keyboard focus without reserving a header row,
46
+ alongside a rounded border around the iframe and its resize grip. These use the
47
+ same 4px frame and 6px toolbar radii as the Mermaid preview controls.
48
+ The toolbar and border fade in and out over 200ms with `ease-in-out`, matching
49
+ Markdown preview. Reduced-motion preferences disable these transitions.
50
+ The toolbar always provides an exit button in full view. Escape also exits when handled
51
+ by the injected iframe bootstrap (nested remote pages can use the message below).
52
+ Resize handles support pointer capture and Arrow Up/Down. After a user resize,
53
+ content-size reports no longer override the chosen height; **Automatic height**
54
+ returns control to the content. Full view always uses the host viewport height.
55
+ Presentation changes never edit the Markdown source.
56
+ The host iframe uses the page theme's background in every presentation mode.
57
+ Guest HTML can remain transparent to show that color without revealing the
58
+ Markdown document behind it in full view.
59
+
60
+ Cooperative pages can advertise the same preferences with
61
+ `parent.postMessage({ type: 'vscode-markdown-editor::presentation', preferences }, '*')`.
62
+ Explicit descriptor properties override guest preferences, including `false`.
63
+ The host sends `{ type: 'vscode-markdown-editor::presentation', mode }` to the
64
+ iframe on binding, loading, and mode changes. Modes are `inline`, `full-width`,
65
+ and `full-view`; pages measure dimensions with their own resize observer.
66
+ Pages should check `event.source === parent` before accepting this notification.
67
+ Send the same message type with `action: 'request-state'` to request the current
68
+ mode, or `action: 'exit-full-view'` to leave full view.
69
+
70
+ The **Experimental / Embedded presentation** group contains page-sized fixtures,
71
+ each with one embedded editor. Provider controls, guest preferences, and explicit
72
+ provider overrides use a release-notes editor backed by the sample web editor's
73
+ text mode. Its borderless red background
74
+ distinguishes guest UI from the Markdown host's themed controls. Edits update the underlying Markdown
75
+ block through the real web editor protocol. **Transparent editor** shows only
76
+ centered text, with no guest background or border, and a host-owned height-resize
77
+ grip. Embedded mode fills the browser; Explorer page mode supports desktop/mobile
78
+ viewports. Their light/dark Playwright tests exercise scaled-preview alignment,
79
+ drag capture, minimum height, size-report precedence, full-width
80
+ and full-view geometry, mode notifications, state retention, and restoration.
81
+
82
+ **Red editor (hover)** and **Transparent editor (hover)** default the `mockHover`
83
+ fixture input to true for deterministic visual verification. This sets
84
+ `data-md-iframe-hover="true"` on the fixture container, exercising the same chrome
85
+ visibility rules as real hover/focus without moving the pointer. Set the input
86
+ to false to test natural hover behavior.
87
+ The `animations` fixture input defaults to false for stable screenshots. Set it
88
+ to true to exercise the production fades, with either real or mocked hover.
89
+
90
+ ## Browser tests
91
+
92
+ `pnpm test:e2e` builds the fixture dependencies and runs the Playwright interaction
93
+ suite. Tests use isolated browser contexts and run in parallel, with four workers
94
+ in CI (override with Playwright's `--workers` option). This is separate from
95
+ Component Explorer's screenshot renderer, which already defaults to available
96
+ CPUs capped at eight workers. Screenshot computation remains Turbo-cached.
97
+
98
+ The Playwright `e2e/milestone-one.spec.ts` suite is temporarily skipped to unblock
99
+ CI while its debug-panel-based checks are replaced with fixture-native assertions.
100
+ The milestone fixtures remain in the screenshot pipeline; other E2E and unit
101
+ tests remain enabled.
102
+
103
+ In locked mode, submitting a comment or dismissing it with Escape keeps the input
104
+ hidden for the current selection, including across layout changes. Changing the
105
+ selection makes comment entry available again.
106
+
107
+ ## Editing policies
108
+
109
+ The editor has three independent policies:
110
+
111
+ - `EditorModel.readonlyMode`: disables text editing. The lock/pencil button
112
+ changes only this permission, not parsing, layout, or syntax visibility.
113
+ - `EditorModel.presentation`: `reading` uses conventional Markdown whitespace;
114
+ `editing` preserves source line breaks, blank lines, repeated spaces and tabs
115
+ while keeping rich formatting. Both work while locked or editable.
116
+ - `EditorModel.renderMarkupText`: `none` hides syntax, `selection` uses the
117
+ existing active-block revelation rules, and `all` reveals syntax throughout
118
+ the document. Hidden inline syntax occupies no width in either presentation.
119
+ Find can temporarily reveal hidden source for the current match without
120
+ changing this policy or cursor-navigation rules; closing Find removes the reveal.
121
+
122
+ Non-obvious spaces (leading, trailing, and runs of two or more) show dot
123
+ indicators wherever markup is revealed; tabs show an arrow. Ordinary single
124
+ inter-word spaces remain unmarked. The view-data layer maps markup visibility
125
+ to per-leaf `showWhitespace` booleans, so whitespace rendering does not depend
126
+ on the public policy enum. In editing presentation these are overlays on real
127
+ source whitespace: line breaks, wrapping, source offsets, and clipboard text
128
+ remain unchanged.
129
+
130
+ The model defaults to editable, reading presentation, and selection markup.
131
+ Hosts can initialize presentation with `EditorView`'s `presentation` option.
132
+ Switching presentation reparses without changing source or selection. Locking
133
+ does not reparse the document.
134
+
135
+ Authoritative host updates preserve selection identity and caret affinity when
136
+ the mapped endpoints are unchanged, avoiding a second render for an identical
137
+ edit echo. Pending-paragraph state is still cleared and stale offsets are clamped.
138
+ Rich-link layout batches DOM reads and writes across independent blocks, while
139
+ keeping dependent pills within a paragraph or table sequential.
140
+
141
+ The editing preset maps to explicit parser options rather than a parser UI mode:
142
+ `explicitBlankLineBlocks`, `explicitFinalEmptyLine`,
143
+ `preserveTrailingListContinuation`, and `allowEmptyListItemInterrupts`.
144
+ Blank source lines become empty blocks; the final unfinished line stays in its
145
+ list paragraph when it can continue that paragraph. A blank line ends this lazy
146
+ continuation. Empty nested list prefixes are structural before their first text
147
+ character is typed. These narrow recognition deviations avoid structural jumps
148
+ while typing or deleting, without depending on editing history. The reading
149
+ preset leaves these extensions off.
150
+
151
+ Editing presentation recognizes empty task checkboxes (`[ ]`, `[x]`, and `[]`)
152
+ at end-of-file as well as before whitespace or a newline. Enter exits an empty
153
+ task just like an empty list item; a nested empty task first outdents.
154
+
155
+ In editing presentation, Enter continues complete list items and exits/outdents
156
+ empty ones. Elsewhere it inserts a single newline without creating a virtual
157
+ paragraph. Arrow Down also stays within
158
+ the existing source instead of creating a virtual paragraph.
159
+
160
+ At a soft wrap, the end of one visual line and start of the next share a source
161
+ offset. Mouse placement and visual-line navigation retain upstream caret
162
+ affinity when targeting the preceding line, without changing source offsets.
163
+ Explicit newlines and programmatic selections keep their existing behavior.
164
+
165
+ See [Editing property tests](docs/editing-properties.md) for the interactive
166
+ assertion fixtures, CI contracts, deterministic editing corpus, and the
167
+ red/green/visual evidence required for layout fixes.
168
+
169
+ ### Rich link display hints (experimental)
170
+
171
+ Inline Markdown link titles can opt into two rendering experiments:
172
+
173
+ - `[commit](commit:1234567 "md:max-width=32")` bounds the entire pill to `32ch`.
174
+ - `[commit](commit:1234567 "md:icon-only")` shows only the resource icon, retaining
175
+ its tooltip, accessible name, keyboard activation and original destination.
176
+
177
+ Only these complete title values are interpreted as hints; other titles do not
178
+ change pill rendering. The source title and URL remain editable and unchanged.
179
+ These are fixture-driven experiments, not an extension contribution API.
180
+
181
+ ### Automatic links
182
+
183
+ Bare HTTP(S) URLs, `www.` addresses, and email addresses are detected using GFM
184
+ autolink rules. Angle-bracket autolinks are supported too. They use the same link
185
+ nodes and host presentation provider as explicit Markdown links, so recognized
186
+ GitHub issue URLs can receive VS Code rich presentations without `[label](url)`
187
+ syntax. Active links retain their exact editable source. Code spans and fenced
188
+ code do not auto-link; trailing punctuation is excluded from link targets.
189
+
190
+ Pasting a single HTTP(S) URL over selected inline text turns that selection into
191
+ a Markdown link, preserving inline formatting. Pasting without a selection,
192
+ inside code or an existing link, or across block boundaries keeps ordinary text
193
+ replacement. Ctrl/Cmd+Shift+V bypasses link creation with the async clipboard
194
+ strategy (including VS Code). Typed URLs do not wrap selections.
195
+
196
+ ### Fixture reference rendering
197
+
198
+ The real-editor examples expose **Interactive mode** in the fixture toolbar.
199
+ This live input enables browser typing and cursor blinking without resetting the
200
+ editor, selection, or recording; it defaults off for deterministic screenshots.
201
+
202
+ In the sidebar, **showEditModeToggle** restores the editor's lock button.
203
+ **readonly**, **presentation** (`reading` / `editing`), and **renderMarkupText**
204
+ (`none` / `selection` / `all`) control permission, presentation, and markup policy.
205
+ In read-only mode, `selection` keeps Markdown rendered even when text is selected;
206
+ unlocking restores selection-driven source markers. Explicit `all` still exposes
207
+ source for inspection, and diff/find diagnostics retain their explicit source reveals.
208
+ These controls are
209
+ live in the examples, main demos, content/selection fixtures, and Mermaid
210
+ fixtures: edits and selection survive changes. The dropdown follows the editor
211
+ button and replayed state. Legacy recordings with `sourceEditing` and
212
+ `activeEverywhere` are translated when loaded; new recordings store the explicit
213
+ policies. Existing fixture defaults are preserved, including
214
+ hidden buttons in the examples and selection fixtures. Browser input remains
215
+ controlled separately by **Interactive mode**.
216
+
217
+ The Compare diagnostics (injectivity, Arrow Down coverage, and active/inactive
218
+ comparison) also honor **showEditModeToggle**, defaulting to hidden. They retain
219
+ their diagnostic selection/presentation setup rather than synchronizing the
220
+ compared models through the interactive policy controls.
221
+ Injectivity activates each source character and compares its start and end in
222
+ that same layout. It does not compare coordinates across cell activations,
223
+ which can resize table columns when inline markers appear or collapse.
224
+
225
+ The **Reference** debug tab follows the editor's source and replay position.
226
+ Its renderer selector defaults to **GitHub (cmark-gfm)**, using the
227
+ [`@vscode/cmark-gfm-wasm`](../../../cmark-gfm-wasm/README.md) workspace package
228
+ with GFM tables, task lists, strikethrough, and autolinks. **markdown-it** retains
229
+ the previous reference engine for comparison (including its different task-list
230
+ behavior). Both use `github-markdown-css` in a script-disabled iframe.
231
+ Ordinary paragraphs and lists therefore usually look identical. For a visible
232
+ engine difference, try `- [x] Done`: cmark-gfm renders a checkbox, while the
233
+ markdown-it configuration leaves `[x]` as text.
234
+ This is GitHub's Markdown parser plus reference CSS, not GitHub's complete site
235
+ rendering/sanitization pipeline. cmark's safe HTML defaults remain enabled.
236
+ Initialization/rendering errors are displayed in the pane, not replaced by a
237
+ different engine.
238
+
239
+ The **Trees** pane groups **AST**, **Tokens**, **View data**, and **View** behind
240
+ a dropdown. **Split right** opens another copy with an independent tree
241
+ selection; both continue to follow live edits and replay. **Layout** remains a
242
+ separate pane.
243
+
244
+ Fixture dependency builds now require Emscripten 4.0.15 and CMake for cmark-gfm.
245
+ Alternatively, build that package with `pnpm --filter @vscode/cmark-gfm-wasm
246
+ build:docker`, then run `pnpm run build:fixtures` once the other fixture
247
+ dependencies have been built. Production editor bundles do not include this
248
+ fixture-only renderer.
249
+
250
+ ### Language feature widgets
251
+
252
+ `RenameModel` / `RenameWidget` and `SuggestModel` / `SuggestWidget` are isolated,
253
+ observable-driven contributions exported from the package. Import
254
+ `@vscode/markdown-editor/rename.css` or `@vscode/markdown-editor/suggest.css`
255
+ alongside the corresponding widget. Hosts own provider requests, edit application,
256
+ command routing, and editor integration; importing a widget does not enable a
257
+ language feature automatically.
258
+
259
+ See the [rename integration guide](src/contrib/rename/README.md) and
260
+ [suggest integration guide](src/contrib/suggest/README.md) for their contracts,
261
+ focus handling, and lifecycle requirements.
262
+
263
+ ### Milestone 1 editing coverage
264
+
265
+ The `editor/milestone-1` fixture group is generated from
266
+ `src/view/fixture/milestoneOneScenarios.ts`. Each unique source, directed
267
+ selection, and presentation state mounts the real editor directly. Screenshot
268
+ fixtures do not replay editing history: restored undo/redo states and other
269
+ identical states are deduplicated across the group. Behavioral tests cover
270
+ typing and history separately. The star has detailed prefix coverage; other
271
+ list markers each have a sanity state rather than a duplicate sequence.
272
+ Examples default to 360px wide and content-sized height. **Editor margin**,
273
+ width, markup policy, read-only toggle, and interactive input controls support
274
+ live visual review. Disabling editor margins retains a left rail for hanging
275
+ source markers. The source-selection status uses a fixed-height, ellipsized row.
276
+
277
+ | Family | Source-state coverage | Still to verify before milestone completion |
278
+ | --- | --- | --- |
279
+ | Text and whitespace | Spaces, tabs, soft line breaks, graphemes, empty/populated documents, directed selections | Empty-file VS Code integration, wrapping, clipboard and IME |
280
+ | Headings | ATX levels 1/2/6, setext creation, incomplete prefixes | Other levels, Enter/split/join and caret geometry across wrapping |
281
+ | Lists | Star prefixes, alternate-marker sanity checks, lazy continuation, stable blank-line carets, source/bullet alignment, list continuation/exit | Broader active-block ownership, indentation/outdent and nested combinations |
282
+ | Task lists | Checked/unchecked prefixes, nested task ownership, mixed sibling label alignment | Toggling/history and broader nested task transitions |
283
+ | Links | Inline destination completion, reference definition completion, formatted labels | Escapes/titles, selections at boundaries, mouse placement and wrapped labels |
284
+ | Inline formatting | Star/underscore italics, bold, strike, bold-italic and nested/adjacent emphasis | Delimiter boundary matrix, escaped punctuation, cross-line and cross-block selections |
285
+
286
+ These are coverage seeds, not a claim that every combination is complete.
287
+ Concrete feedback must receive a named fixture plus behavioral/geometry
288
+ assertions; screenshot review alone does not close a regression.
289
+ Point-to-source resolution always uses the editor's geometric hit test. The
290
+ demo's `caretDomPosArea` visualization samples that same path; the browser
291
+ caret hit-test alternative and its `caretDomPos`/`ownHitTest` switches are removed.
292
+
293
+ The list-marker review deliberately distinguishes presentation policies.
294
+ Editing presentation treats a bare `*`, `-`, `+`, or ordered marker as paragraph
295
+ text until its separator space/tab exists. A standalone bare marker has no list
296
+ indentation; a bare continuation stays in its existing bullet paragraph. Inserting
297
+ the separator promotes it, and deleting the separator reverses that promotion.
298
+ Reading presentation retains CommonMark empty-list parsing, with the caret
299
+ immediately after the actual marker rather than its synthetic gutter separator.
300
+ The `lists/lazy-continuation` checkpoints, parser ownership tests, and browser
301
+ geometry/history regressions cover these differences.
302
+
303
+ The `lists/empty-line-0` and `lists/lazy-empty-line`
304
+ checkpoints compare the same caret at EOF and before an additional newline,
305
+ without duplicate restored-state screenshots. The first empty continuation line of a
306
+ populated list paragraph keeps its indentation even when another newline follows;
307
+ the final line after the list terminator remains outside the list. Empty
308
+ marker-only items retain their existing empty-line placement.
309
+
310
+ `lists/nested-indent-dots` spreads source indentation indicators over the reserved
311
+ gutter. `lists/indent-space-1`, `lists/indent-space-2`, and `lists/indent-space-3`
312
+ isolate individual space selections and caret movement before a list marker.
313
+ Parsed list depth determines the gutter width; the actual source whitespace
314
+ shares that width evenly, rather than assuming two spaces per nesting level.
315
+ Each space's source-mapped text and dot fill its allocated cell. At the final
316
+ indentation boundary the caret meets the actual source glyph, including its
317
+ normal font sidebearing; the selection spans those exact caret endpoints.
318
+ Source-only spaces that do not create a nested item extend
319
+ leftward into the gutter instead of moving the logical bullet or text column.
320
+ Their display width uses the parsed marker prefix, not a fixed two-space rule.
321
+ Native bullet typography is unchanged.
322
+ Nested task prefixes use half-width indentation cells to bring their first
323
+ source-space dot closer to ordinary siblings without moving bullets, checkboxes,
324
+ or labels. Unordered-list continuation indentation reuses the source prefix's
325
+ indentation, marker, and separator cells, including an indented blank line
326
+ following a list. `lists/mixed-prefix-spaces`, `lists/blank-continuation-spaces`,
327
+ and `lists/nested-continuation-spaces` isolate these cases; browser tests verify
328
+ each cell's geometry, clicking, arrow movement, and typing at four font sizes.
329
+ Within a list paragraph, continuation whitespace takes no space while the block
330
+ is inactive. Activating the block reveals the source prefix inline so its spaces
331
+ indent the continuation text, rather than placing them in a left-hand gutter.
332
+ Quote prefixes and list indentation on the same line share that source span.
333
+ Whitespace remains source-mapped for clicking, navigation, and editing.
334
+ The `lists/same-paragraph-continuation`, `lists/nested-same-paragraph-continuation`,
335
+ `lists/quoted-same-paragraph-continuation`, and
336
+ `lists/uneven-same-paragraph-continuation` fixtures show inactive and active
337
+ states. Browser tests cover ordinary, ordered, task, nested and quoted lists,
338
+ tabs, CRLF, hard breaks, wrapped lines, cross-line formatting/link labels,
339
+ multiple continuation lines, prefix clicks, and caret movement.
340
+ Reading presentation retains its existing soft-line-break behavior.
341
+ The `lists/indent-depth-2`, `lists/indent-depth-uneven`, and `lists/indent-prefix-3`
342
+ fixtures reconstruct the nested-spacing feedback with minimal sources.
343
+ Browser geometry assertions also cover wider ordered prefixes, tabs,
344
+ markup visibility, typing, and undo.
345
+
346
+ Typing the third backtick of a new opening fence inserts a closing fence on the
347
+ next line, leaving the caret on the opener for a language name. Container
348
+ indentation is retained; pasted, inline, already-paired, and closing fences are
349
+ not completed. `formatting/fence-completion` captures the typing states. Enter
350
+ inside a fenced block preserves its indentation in editing presentation.
351
+ `lists/fenced-continuation-spaces` covers list-contained fences: opening, code,
352
+ and closing-line spaces reuse the list's source prefix cells, including with
353
+ syntax highlighting, without changing reading presentation.
354
+
355
+ The mode toggle is a sticky overlay in the right margin when there is room,
356
+ and may overlap content in narrow editors. It never changes text flow or reserves
357
+ an asymmetric rail. Left and right padding use two fixed sizes, without
358
+ intermediate scaling: 8px through 600px editor width, then 48px.
359
+ Once the document reaches its configured maximum width (900px by default),
360
+ it stays that width and additional space goes equally to the outer margins.
361
+ Disabling the width limit keeps the 48px margins on wide editors.
362
+ Top-level headings retain
363
+ symmetric 2em minimum clearance for their hanging markers, independent of heading
364
+ level. Long prefixes scale to fit that gutter instead of moving the document.
365
+ Top padding stays at 32px regardless of editor width, document content, or
366
+ scrollbar gutters. The VS Code webview uses thin scrollbars with transparent
367
+ tracks. First and later headings use identical font line boxes, preserving the
368
+ same text-to-underline spacing. Only the first block's top margin is removed;
369
+ the fixed top padding provides the optical spacing adjustment.
370
+ The `editor/readonly-toggle/` fixtures include wide, narrow, and scrolling documents.
371
+ The first block has no additional top margin in either presentation. The first
372
+ block inside a blockquote also has no top margin, preventing it from collapsing
373
+ outward and shifting the quote when a marker-only line gains content. The
374
+ paired `HeadingReading` and `HeadingEditing` fixtures and lock-toggle browser
375
+ tests guard its text position. VS Code themes map whitespace indicators to
376
+ `editorWhitespace.foreground`, including its dark-theme opacity.
377
+ The `HeadingSpacingH1` and `HeadingSpacingH2` fixtures compare first and later
378
+ headings; browser tests cover their underline spacing across themes, widths,
379
+ wrapped text, presentation modes, and active or inactive selections.
380
+
381
+ An empty, unlocked document in editing presentation always keeps a collapsed
382
+ selection at offset zero and its active typing block, including after Escape,
383
+ margin clicks, clicks below the document, and external selection clearing.
384
+ This does not take keyboard focus: the caret blinks only while the editor is
385
+ focused. Reading and locked documents keep their existing selection behavior.
386
+ `EmptyEditing` captures the empty surface; browser tests verify actual blink
387
+ cycles, blur, typing/deleting, and presentation/lock transitions.
388
+
389
+ Escape clears an existing selection, including a collapsed caret, and cancels
390
+ any pending empty paragraph. The exception is an empty, unlocked editing
391
+ document: Escape keeps its caret and active block visible so typing can begin
392
+ immediately. Open find/suggestion widgets and focused nested
393
+ controls handle Escape first. With no selection or pending paragraph, Escape
394
+ remains available to the host. Clicking beside or below a non-empty document also
395
+ clears selection. The empty-document invariant is enforced by the view, not by
396
+ individual input handlers. The editor fills a fixed-height host so its blank
397
+ area receives the same pointer handling as the margins, without stretching the
398
+ document itself.
399
+
400
+ Set `EditorViewOptions.highlightActiveBlock` to `false` to disable the active
401
+ block's background and glow, or update the live `view.highlightActiveBlock`
402
+ observable. This does not change active source markers, selection, or editing
403
+ behavior. Highlighting is enabled by default.
404
+
405
+ Empty ATX headings keep their separator whitespace in the same source marker
406
+ as populated headings. A zero-source-length line anchor preserves the heading
407
+ caret's column and line box when typing or removing its first character.
408
+ The existing `headings/atx-*` empty-prefix states and `headings/prefix-caret-*`
409
+ populated states cover this transition.
410
+
411
+ Rich-link pills at the end of a rendered line may use the remaining content
412
+ width before truncating their title. Short titles retain their natural width;
413
+ pills followed by prose retain their compact title limit. The width is updated
414
+ with editor layout and metadata changes, respecting list indentation and the
415
+ mode toggle's local exclusion rectangle. `Links/Rich links/Static/Trailing titles`
416
+ provides long, short, mid-line, formatted, and session-link examples.
417
+
418
+ `task-lists/nested-task-ownership` prevents a child checkbox from turning its
419
+ parent into a task. `task-lists/mixed-task-labels` aligns labels of task and
420
+ ordinary sibling items while preserving nested indentation. The ordinary
421
+ item's real separator spaces expand to fill the extra label-column width.
422
+ `task-lists/nested-task-empty-sibling-*` and `task-lists/mixed-task-separated-lists-*`
423
+ isolate populated/empty nested tasks and mixed lists separated by a thematic break.
424
+ In both presentations, checkboxes and ordinary bullets share a gutter column;
425
+ the task's extra source bullet hangs farther left. Labels keep their position
426
+ when markup visibility changes, except for extra source-only separator padding.
427
+ `lists/mixed-label-column` compares the same nested labels in reading and editing;
428
+ both use one shared marker/label layout rather than separate task indentation.
429
+ The expanded ordinary separator has a visible
430
+ whitespace dot. Browser tests click task-label characters with markup hidden,
431
+ selection-revealed, and fully revealed, then verify exact insertion offsets.
432
+ `task-lists/empty-task-label` keeps a completed checkbox plus separator structural
433
+ before label text is typed in editing presentation. Reading retains standard GFM
434
+ parsing. Typing or deleting the label must not change checkbox ownership or move
435
+ the empty caret's text column.
436
+ The `task-lists/checkbox-separator-*` fixtures cover the caret immediately
437
+ after `]`, after its first separator, and after an extra space. In editing
438
+ presentation, the first separator reaches the label column and each additional
439
+ space advances normally. The checkbox and separator participate in inline layout,
440
+ so native text ranges, clicks, and the painted caret agree.
441
+ The `lists/extra-separator-continuation-spaces` and
442
+ `lists/extra-separator-blank-line` fixtures protect `- test` followed by a blank
443
+ line and ` test`: the four prefix cells align and the blank-line caret sits
444
+ at the active label column.
445
+ Editing also treats `- [] test` as unchecked, preserving task layout through
446
+ `[x]` -> `[]` -> `[ ]` edits without inserting a space into the source.
447
+ Reading keeps `[]` as literal text. The normal separator requirement remains;
448
+ `- []text` and a bare `- []` at EOF are not tasks. The reduced
449
+ `task-lists/checkbox-value-0`, `checkbox-value-1`, and `checkbox-value-2`
450
+ fixtures cover all three values in reading and editing presentation.
451
+ The paired `task-lists/checkbox-line-end-empty` and `task-lists/checkbox-line-end-filled`
452
+ states extend this guarantee to a checkbox followed directly by a newline:
453
+ typing on the continuation must neither create the checkbox nor move the caret
454
+ column. Bare `* [ ]` without a separator retains standard non-task parsing.
455
+ `lists/nested-parent-filled` and `lists/nested-parent-empty` preserve the parent's
456
+ marker separator and its own visual line when its last label character is
457
+ removed. The nested item's row and indentation must not move. Parser tests cover
458
+ LF, CRLF, CR, source tiling, and incremental edits; browser tests assert the
459
+ caret and nested-item coordinates through deletion and retyping.
460
+ `task-lists/continuation-enter` covers a blank task continuation: editing Enter
461
+ inserts only `\n`. List auto-continuation still applies on the item's own marker
462
+ line, not every later line owned by that item.
463
+ `task-lists/checkbox-newline` keeps the line ending after `[ ]` in paragraph
464
+ flow instead of the absolutely positioned checkbox source.
465
+ The paired `lists/marker-separator-1` and `lists/marker-separator-2` states keep
466
+ the marker fixed while additional separator characters move the content right.
467
+ Extra spaces occupy half-em source cells. Unordered source symbols use the
468
+ body font and baseline in a one-em gutter cell, followed by a half-em separator.
469
+ Native reading bullets retain their original size, font, and nested list style;
470
+ we do not require unrelated glyphs to have identical pixel ink bounds across
471
+ fallback fonts. Geometry tests cover multiple font families and sizes.
472
+
473
+ `lists/separator-activation-inactive` and `lists/separator-activation-active`
474
+ show that extra source-only separator padding collapses outside the active
475
+ item. Nested logical gutters do not move. The explicit `all` and `none` markup
476
+ policies still force revelation or hiding.
477
+ `formatting/table-cell-outside`, `formatting/table-cell-first`, and
478
+ `formatting/table-cell-second` keep inline formatting markers out of the
479
+ inactive cells' layout. Activating another cell may resize columns; source
480
+ offsets remain distinct within each resulting layout.
481
+
482
+ `lists/blank-between-items` keeps list-level blank separators at their
483
+ surrounding container's margin, including typing and undo/redo. These separators
484
+ are distinct from continuation lines within a list-item paragraph.
485
+ `lists/first-blank-between-items` covers the first empty line immediately after
486
+ populated item text: its caret stays at the item's text indent, even when later
487
+ blank lines and another item follow. Moving to the next blank line moves to the
488
+ document margin. Browser tests compare each empty-line caret with the position
489
+ of ordinary text inserted there, across nested, ordered, and quoted lists,
490
+ empty items, and LF and CRLF endings. Parser source-ownership and incremental
491
+ reconciliation tests also cover CR endings.
492
+
493
+ `listMarkerAlignment/Source versus rendered bullet` places the production
494
+ source star and rendered bullet side by side. Reading bullets retain native
495
+ browser typography; source symbols use the body font and baseline, centered
496
+ in a one-em gutter slot. Browser regressions check the source column and
497
+ baseline across font families and sizes, and ensure reading bullets are not
498
+ replaced or resized. They deliberately do not require optical ink centers to
499
+ match across different glyphs and fallback fonts.
500
+
501
+ ### Screenshot CI
502
+
503
+ All fixtures use the package-local `fixtureUtils.ts` wrapper to disable CSS
504
+ animations and transitions within their own container, including the mode
505
+ toggle and cursor. Editor examples restore live motion when **Interactive mode**
506
+ is enabled. The custom cycling highlighter stays fixed by default; enable
507
+ **Cycle keyword colors** to exercise its live observable updates. Property
508
+ fixtures keep their complete diagnostic evidence in a **Replay log** disclosure,
509
+ collapsed by default so screenshots capture the editor rather than the JSON.
510
+
511
+ The `markdown-editor-screenshots` workflow captures every fixture discovered
512
+ by this package's Component Explorer configuration, including content,
513
+ diagnostic, property, and widget fixtures. There is no fixture-ID allowlist
514
+ or fixed fixture count; new fixtures are included automatically. The separate
515
+ screenshot job runs for every PR targeting main and every push to main, without
516
+ changed-path gates. Turbo decides which computations can be reused; commit
517
+ manifest publication always runs for authorized events, including cache hits.
518
+ This registers unchanged snapshots for new commit SHAs so unrelated main
519
+ changes do not leave holes in the baseline history. Pages reload
520
+ between fixtures to isolate their rendering. Capture uses `build:fixtures`
521
+ and `component-explorer.screenshots.json` to serve a static build on an
522
+ ephemeral local port, avoiding dev-server dependency optimization and module
523
+ reloads during a full run. Interactive exploration still uses the regular
524
+ `component-explorer.json` dev-server configuration. On the hosted Ubuntu runner,
525
+ CI removes the unused Android SDK before installing dependencies to leave
526
+ disk space for the full capture. The package-owned
527
+ `scripts/screenshots.mjs` and `screenshots` task store their
528
+ validated render JSON (without Git commit metadata or PNGs) in Turborepo's
529
+ cross-commit cache. The publish step attaches the *current* commit and the
530
+ `markdown-editor` service project ID, checks each content hash against
531
+ the screenshot service, and uploads only missing PNGs. If a cached hash is
532
+ missing remotely and its PNG is no longer local, it recaptures and verifies
533
+ the image before publishing; unchanged hashes already present remotely need
534
+ no browser run on a cache hit.
535
+
536
+ CI restores only the published-cache namespace and saves it after successful
537
+ image/manifest upload and validation. Fork and Dependabot runs do not populate
538
+ this cache. Thus a reusable CI snapshot's images have already been published;
539
+ PNGs remain in the service rather than in Turbo. The existing missing-image
540
+ recovery also supports local caches and older snapshots.
541
+
542
+ Before consulting the screenshot cache, CI builds the renderer and its
543
+ dependencies with `pnpm --filter @vscode/component-explorer-cli... run build`.
544
+ This deliberately bypasses Turbo's dependency-build cache: package builds can
545
+ also generate external dependency outputs (such as LinkRPC), which are not
546
+ restored by a cache entry containing only that package's `dist` directory.
547
+ Screenshot render results remain cached across commits.
548
+ Screenshot-script tests and fixture typechecking are explicit Turbo tasks.
549
+ Rendering depends on successful fixture typechecking, including on cache hits;
550
+ unchanged successful checks need not execute again. Commit metadata, service
551
+ publication, and PR reporting remain outside the cached tasks.
552
+
553
+ Image lookups run in batches of at most eight, use a 10-second timeout per attempt, and retry transient network
554
+ errors and HTTP 429/500/502/503/504 responses up to three total attempts, with
555
+ one- and two-second backoffs logged to CI. Only HTTP 404 means an image is
556
+ missing; persistent service errors fail preparation rather than being hidden.
557
+ The workflow archives the per-commit manifest and writes an advisory
558
+ base-commit comparison with before/after screenshots to the job summary.
559
+ Non-fork PRs also get a single updated comparison comment. Large comments
560
+ stop at a complete fixture boundary below GitHub's size limit; the complete
561
+ comparison remains in the job summary and `markdown-editor-screenshot-report`
562
+ artifact. Comparisons can
563
+ read the legacy `markdown-editor-pilot` baseline until main has published
564
+ under the new project ID; new uploads always use `markdown-editor`.
565
+ Diagnostic fixture errors remain in the published manifest and are listed
566
+ explicitly in the comparison report, including fixtures that could not produce
567
+ an image. Every fixture must pass diagnostics for required CI to be green,
568
+ including when its result is restored from the screenshot cache. The diagnostic
569
+ check runs after artifacts, uploads, reports, and PR comments so failures retain
570
+ their evidence. Run `node scripts/screenshots.mjs check` locally to check the
571
+ cached manifest, or pass a manifest path to check a specific capture.
572
+ Visual screenshot changes remain advisory, not merge gates.
573
+ Empty, duplicate, or malformed manifests, infrastructure failures, and
574
+ invalid image hashes still fail CI.
575
+
576
+ The service must support its existing `/images/<sha256>`,
577
+ `/commits/<owner>/<repo>/<sha>`, and authenticated `/upload` endpoints, with
578
+ project snapshots enabled. The upload step sends the manifest and
579
+ missing PNGs as a ZIP directly; no service-side GitHub token is needed to
580
+ download Actions artifacts. Main-branch pushes and non-fork PRs authenticate using GitHub
581
+ Actions OIDC (the same mechanism used by VS Code screenshot CI), not a new
582
+ repository secret. The service must authorize this repository's main pushes
583
+ and non-fork PR tokens for its OIDC audience. Fork and Dependabot PRs skip
584
+ OIDC upload and commenting; their job summary links to the workflow artifacts
585
+ instead of embedding unpublished images. Upload or unexpected baseline-service
586
+ errors fail CI explicitly, while artifacts are preserved for inspection.
587
+ The reusable `markdown-editor-screenshots` workflow handles rendering,
588
+ archiving, upload, and reporting for both callers. Both `required` and the
589
+ main-push-only `markdown-editor-screenshots-publish` caller explicitly grant
590
+ `contents: read`, `id-token: write`, and `pull-requests: write`; reusable
591
+ workflows cannot elevate their caller's permissions.
592
+ The workflow's
593
+ public service URL and runner/font configuration must be updated together
594
+ when changing service or rendering environment; renderer source, fixture
595
+ inputs, dependency lockfile, and the runner image version participate in
596
+ the Turbo cache key. Branch protection still requires the repository
597
+ administrator to mark the umbrella `Required / required` check as required.
598
+
599
+ Screenshot orchestration, contract tests (`pnpm run test:screenshots`), and
600
+ the Turbo task configuration live in this package. Component Explorer provides
601
+ the reusable renderer and screenshot service, with no Markdown-specific scripts.
602
+ The `markdown-editor-screenshots` artifact contains the upload manifest and new
603
+ PNGs; `markdown-editor-screenshot-render` preserves render diagnostics.
604
+
605
+ Start with the [current implementation specification](./docs/specification.md).
606
+ It explains the architecture, runtime data flow, public integration surface,
607
+ extension points, the VS Code custom text editor adapter, tests, and current
608
+ limitations.
609
+
610
+ Other package documents:
611
+
612
+ - [Original design](./docs/design.md) - historical design intent.
613
+ - [Original implementation plan](./docs/plan.md) - historical roadmap; its
614
+ checkboxes do not describe current completion.
615
+ - [Engineering backlog](./docs/todo.md) - known issues and future work.
616
+
617
+ From this package directory:
618
+
619
+ ```sh
620
+ pnpm test
621
+ pnpm test:e2e
622
+ pnpm build
623
+ pnpm build:explorer
624
+ ```
625
+
626
+ Playwright reuses test servers from the same worktree using a deterministic
627
+ worktree-specific port pair. Set `VSCODE_MARKDOWN_EDITOR_E2E_BASE_PORT` to the
628
+ first port of a different adjacent pair if the generated ports conflict with
629
+ another local service.
630
+
631
+ From the `vscode-packages` workspace root, run `tilt up` to supervise the editor
632
+ test page in Tilt's local web UI at http://localhost:10350. Tilt checks the
633
+ workspace installation with `pnpm install --frozen-lockfile` first; the `editor`
634
+ resource then serves the test page at its worktree-specific URL (shown in the
635
+ Tilt UI). Enable the optional `fixtures` resource in the UI to build and serve
636
+ the Component Explorer fixtures on the adjacent port. The servers run locally;
637
+ Docker and Kubernetes are not needed. Disable either server in the UI to stop
638
+ it, or run `tilt down` to stop Tilt's resources. To start only the fixtures
639
+ from the command line, use `tilt up fixtures`.
640
+
641
+ Install [Tilt](https://docs.tilt.dev/install.html) and use a Node installation
642
+ that supports TypeScript type stripping (Node 22.6+). Corepack runs the pnpm
643
+ version pinned in the workspace's `package.json`. The same
644
+ `VSCODE_MARKDOWN_EDITOR_E2E_BASE_PORT` override applies to both Tilt servers.
645
+
646
+ The multi-repository workspace also has a `Tiltfile` beside
647
+ `markdown-editor.code-workspace`. Run `tilt up` from that directory to see the
648
+ package resources and optional VS Code resources in one dashboard. From either
649
+ dashboard, enable `markdown-explorer` for the interactive Component Explorer
650
+ (port 5339), `explorer-demo` for its demo app (port 5174),
651
+ `markdown-package-watch`, or `team-tools-watch`. The `fixtures` resource above
652
+ is a built preview, not the interactive Component Explorer CLI server. Enabling
653
+ `markdown-explorer` requires stopping any separately launched Component
654
+ Explorer daemon for this same project first; Tilt will not terminate it.
655
+
656
+ In the multi-repository dashboard, `vscode-markdown-webview-watch` and
657
+ `vscode-client-watch` run watchers from the VS Code checkout, while
658
+ `vscode-explorer` serves its separate Component Explorer (port 5337).
659
+ `vscode-extension-host` builds Team Tools and launches Code - OSS with an
660
+ isolated profile, no built-in or installed extensions, and only the Team Tools
661
+ development extension. Disabling the resource closes that window, not other
662
+ VS Code instances. The VS Code checkout needs its own dependencies installed
663
+ before enabling its watchers or Component Explorer.