HMD-0021: The VS Code extension — the HyperMarkDown preview surface¶
Status: drafted Created: 2026-08-06 Source: HyperMarkDown in the editor — Requirements
Companion notes¶
- HMD-0020 —
@hypermarkdown/core, which owns the grammar, the resolver, and the document IR this extension renders. Nothing semantic is decided here. - HMD-0025 — the graph tab: what it draws, how the network view is bounded, and the layout library it is drawn with. This record keeps the tab strip, the message envelope, and the policy it runs under.
Abstract¶
This proposal defines hmd-vsc-ext: a VS Code extension registering
.hmd as its own language with its own TextMate grammar, contributing one
webview view whose internal tab strip carries the preview modes, and rendering
the HMD-0020 document IR into a card-shaped view where embeds stay embeds. It
pins the host/webview split — parse, resolve, and expand in the extension host;
render in the webview — the postMessage protocol between them, the
bidirectional scroll-sync algorithm and its timing constants, the workspace
index and its watchers, the mapping from HMD-0001 lint rules onto VS Code
diagnostics, and a content security policy that forbids remote content. v1
ships the rendered and graph tabs, the graph itself specified in HMD-0025;
mind map is specified only as far as the tab-strip contract. Everything
specified here renders without Python; the language features that will arrive on the Python server of
HMD-0024 are out of scope for this record, and the
preview stays independent of them.
Motivation¶
Sketch §14 states the thesis without hedging: "Preview is the product. Everything else is supporting cast." Two consequences shape this record.
- The
.hmdlanguage identifier is what makes the preview possible. A custom language ID stops the format contending with the built-in markdown language for the preview pane and the grammar, which sketch §15 already identified as sufficient justification for a custom extension on its own. - The surface has to show the hyper layer, not hide it. An embed rendered as anonymous inline prose is indistinguishable from a copy-paste. Rendering it as a labelled, collapsible card that navigates to its origin is the only thing the preview does that a markdown previewer cannot, and it is therefore the feature the whole design is arranged around.
Risk this proposal must not defer: a webview is a script host. It receives content derived from arbitrary files in a cloned repository, and it runs inside the user's editor. §11 is not boilerplate.
Goals¶
- A preview that is live-on-type, scroll-synced, and navigable in both directions between source and render.
- Embeds, red links, and namespace context visible as first-class UI.
- HMD-0001 diagnostics in the Problems panel, from the same code that draws the preview.
- Zero setup: install, open a
.hmdfile, see it rendered. No interpreter, no configuration, no first-run wizard.
Non-goals¶
- A language server, and any use of
vscode-languageclient. Deferred by the requirements document §9. - Semantics. Grammar, resolution, expansion, and diagnostics belong to HMD-0020. Where this extension appears to decide one, it is wrong.
- Rename refactoring (VSX-044) and whole-tree writes of any kind beyond the single create-card action of §5.
- The mind-map tab beyond the tab-strip contract under deferred tabs.
- Web-extension (browser) builds in v1, though HMD-0020's Node-free core keeps the option open.
Specification¶
1. Identity, contributions, activation¶
directory tools/hmd-vsc-ext/
name hmd-vsc-ext
publisher HyperMarkDown
displayName HyperMarkDown
engines.vscode ^1.90.0
language id hmd
extensions .hmd
aliases ["HyperMarkDown", "hmd"]
nameandpublishertogether form the marketplace identifierhypermarkdown.hmd, which is permanent from the first publish. The extension wasvscode-HyperMarkDownwhile the repository used apackages/layout, and was renamed with the move totools/. Nothing has been published, which is the only window in which this rename is free.- The extension MUST contribute the
hmdlanguage rather than reusingmarkdown. Sharing the markdown language ID would put this preview in contention with the built-in one for the same file. - Activation is
onLanguage:hmdplus the view's own activation. The extension MUST NOT use*: a knowledge-base extension that costs startup time in every window will be uninstalled by people who have one.hmdfile. - Commands contributed:
| Command | Title |
|---|---|
hyperMarkdown.openPreview |
HyperMarkDown: Open Preview |
hyperMarkdown.openPreviewToSide |
HyperMarkDown: Open Preview to the Side |
hyperMarkdown.togglePin |
HyperMarkDown: Pin Preview to This Card |
hyperMarkdown.createCardFromLink |
HyperMarkDown: Create Missing Card |
hyperMarkdown.refreshIndex |
HyperMarkDown: Rebuild Index |
- Settings contributed, all under
hyperMarkdown.:
| Setting | Type | Default |
|---|---|---|
root |
string | "" — empty means discover per HMD-0020 §5 |
preview.scrollSync |
boolean | true |
preview.embeds |
"expanded" | "collapsed" |
"expanded" |
diagnostics.scope |
"open" | "workspace" |
"workspace" |
2. TextMate grammar¶
Highlighting is bought from the markdown grammar and extended, not rewritten.
- The grammar's scope name is
source.hmd. Its first pattern MUST be an include oftext.html.markdown, so every markdown construct and every embedded fenced language highlights exactly as it does today. - The format's own constructs take these scopes, which are a stable interface for theme authors:
[[target|display]] meta.link.wiki.hmd
[[ and ]] and | punctuation.definition.link.wiki.hmd
target entity.name.reference.hmd
display string.other.link.title.hmd
![[target]] meta.embed.wiki.hmd
trailing ^id entity.name.label.hmd
--- frontmatter --- meta.embedded.block.yaml, delegating to source.yaml
- Grammar patterns MUST match the construct shapes of HMD-0001 §2 but MUST NOT
be relied on for validity. Highlighting is a hint;
HMD010from HMD-0020 is the verdict. Two matchers will disagree at the edges, and only one of them is normative.
3. Surface¶
- The preview is a
WebviewPanel— a tab in an editor group. The extension MUST NOT contribute a view container, and there is no side-bar surface. An extension cannot default a container to the secondary side bar, so a side-bar preview can only be put in place by a drag the user repeats on every machine; and a reading surface belongs in the same columns as the things being read. - Preview modes are tabs drawn inside the webview, not sibling views. One webview means one IR delivery, one scroll-sync implementation, and one place where state lives; four views would mean four of each.
hyperMarkdown.openPreviewis contributed toeditor/titleunder the context keyhyperMarkdown.hasRoot, so the button is present on every editor group in a knowledge base and absent everywhere else. It MUST open inViewColumn.Active:Besiderefuses to place a tab into a locked neighbouring group and silently splits a third one instead, which is the common case when the other column holds a conversational assistant.hyperMarkdown.openPreviewToSidekeepsViewColumn.Besidefor the side-by-side reading it is named for.- A preview follows the active editor, always, unless the user pins it. This is the surface's defining rule and it MUST NOT be weakened by any heuristic about how the preview was opened: pinning is the absence of following, not a gentler form of it, so a preview that pins itself never follows anything again.
- Following extends to the preview's own navigation. Clicking a
[[wikilink]]MUST move the preview to the target, not only open its source. Browsing by following links is the one thing this surface does that a file viewer cannot. - Several previews MAY be open at once, each titled after its card. Unpinned ones necessarily show the same card; holding a second card is what pinning is for. Opening a preview MUST therefore reuse the unpinned one already in the target column rather than creating a second, and MUST NOT reuse a pinned one.
togglePinacts on the focused preview tab and freezes it on the current card. The frozen state MUST be visible in the UI and MUST be reachable without the command palette — a preview that silently stops following looks like a bug, and a pin nobody can find invites automating it.- Revealing source MUST NOT target the group the preview occupies, and MUST NOT assume column one.
- The extension MUST register a
WebviewPanelSerializerfor the panel's view type, restoring each tab's card. A layout the user arranged and VS Code redrew empty after a reload is indistinguishable from a crash. - Persisted state MUST carry the card and nothing else. In particular the pinned flag MUST NOT survive a reload: workspace storage outlives the build that wrote it, a restored preview that silently stops following is indistinguishable from a broken one, and nothing on screen can tell a deliberate pin from a stale flag. A restored preview follows.
- The header shows the breadcrumb of namespace segments (VSX-005), each segment navigating to that namespace's folder note where one exists.
4. The host/webview split¶
Parsing, resolution, and expansion run in the extension host; the webview renders. The host owns filesystem access and the workspace index, and the webview is deliberately kept incapable of reaching either.
extension host webview
────────────────────────────── ─────────────────────────
@hypermarkdown/core renderer.ts
WorkspaceHost → vscode.workspace.fs IR → DOM
index, resolve, expand tab strip, scroll sync
Document IR ───── postMessage ─────► click handling
◄──── postMessage ─────
DiagnosticCollection
Messages are versioned by HMD-0020's irVersion and pinned here:
host → webview
{ type: "render", irVersion, document, mode, settings }
{ type: "graph", irVersion, graph }
{ type: "revealLine", line }
{ type: "setMode", mode }
{ type: "error", message }
webview → host
{ type: "ready" }
{ type: "openSource", path, line }
{ type: "openTarget", path, fragment }
{ type: "createCard", target, suggestedPath }
{ type: "scrolled", line }
{ type: "modeChanged", mode }
{ type: "graphView", view }
- The host MUST validate every inbound message against this shape and MUST
reject a
paththat does not lie inside the namespace root, even though the webview is the extension's own code. Message handlers are the trust boundary; treating them as internal is how a renderer bug becomes a file-read primitive. - The host MUST NOT send a
renderfor a document it has not fully resolved. A partially-resolved IR would render links as red that are merely not yet computed, and a red link is a work item, not a spinner.
5. Rendering the IR¶
The renderer walks HMD-0020 §7 and does no markdown parsing of its own.
HtmlBlock.htmlis inserted as-is. It is already escaped by the core'shtml: falseconfiguration; the renderer MUST NOT re-enable HTML for it.EmbedBlockrenders as a card: a bordered region with a header naming the source card and fragment, a disclosure control honouringpreview.embeds, and a body that is the recursive rendering ofEmbedBlock.document. Nesting depth is visible in the UI, since a card three levels deep that looks identical to a top-level one is misleading about where the text came from.- The card header navigates to the embedded card's source, not the embedding one (VSX-022).
- An
EmbedBlockwithresolution.state !== "resolved"renders as a card carrying the failure and its rule ID, not as nothing. A silently-dropped embed is the worst available outcome: the document reads as complete and is not. a.hmd-redlinkrenders in the theme's error-adjacent colour and offers create-card. Creating writes exactly one file at the path the resolver would next have searched, seeded with an# H1matching the link text, and the write MUST go throughWorkspaceEditso it lands in the undo stack (VSX-054).- The action MUST NOT prompt for that path. The link already says where the card goes: a bare target becomes a sibling of the card holding the link, an absolute one lands under the namespace root, and a relative one lands where it points. The path is a starting point rather than a commitment — a card is a file and moves like one, and an author who wants it elsewhere from the start writes the link relative. A modal between a red link and a card would charge for the common case to spare the rare one.
a.hmd-ambiguousrenders distinctly from both resolved and red links, and its hover lists the candidates fromResolution.candidates.HMD002is an error the author must arbitrate; showing it as an ordinary broken link would suggest the wrong fix.- All colours derive from VS Code theme CSS variables
(
--vscode-*). No hard-coded palette, so light, dark, and high-contrast themes work without a per-theme stylesheet. - A document larger than 4 MiB is not rendered; the webview shows a notice instead. Reported through the extension's own channel, never as a lint rule (HMD-0020 §9).
5.1 Live editing¶
The preview follows the in-memory buffer, so it is redrawn while its source is in a transiently invalid state. This subsection is what makes that survivable.
- The host MUST render from the unsaved buffer, re-parsing 150 ms after the last keystroke (§6). Saving MUST NOT be a precondition for anything the preview shows.
- The renderer MUST patch the existing DOM against the incoming IR, keyed
by
Block.key(HMD-0020 §7), rather than replacing the document. Blocks whose key and content are unchanged MUST NOT be touched. This preserves scroll position, embed collapse state, and focus across an update (VSX-019), and it is also what keeps the update cost proportional to the edit rather than to the document. - The panel MUST NOT blank, show an error page, or flash an intermediate state between renders. A construct in the middle of being typed renders as literal text — which HMD-0020 §3.2 already requires for malformed input — and the surrounding card renders normally (VSX-018).
- If a render fails outright, the previous render MUST stay on screen and the failure MUST be surfaced without clearing it. Stale content with a warning is strictly better than a blank panel, because the author can still read what they wrote.
- Scroll sync MUST hold its position by source line across a re-render, not by pixel offset. Content above the viewport changes height as it is edited.
- Editing a card that the previewed card embeds MUST re-render the preview, from the embedded card's unsaved buffer where it is open in another tab. An embed is a live window onto another card, and a window that shows a stale copy of a file open two tabs away is a bug the author will not think to suspect.
- Diagnostics run on a slower clock than the preview. Diagnostics for a
document are published 500 ms after the last keystroke, and diagnostics
falling on the line holding the cursor are withheld entirely until the cursor
leaves that line (VSX-033). Every partially typed link is briefly an
HMD001orHMD010; reporting those to the Problems panel in real time would make the panel useless and the editor hostile.
6. Scroll sync and click-through¶
Both directions run off the data-line attributes HMD-0020 §7 requires.
On render, build ANCHORS = [(sourceLine, elementTop)] for every [data-line],
ascending by sourceLine.
editor → preview:
find the pair (a, b) in ANCHORS bracketing the editor's top visible line
scroll to lerp(a.elementTop, b.elementTop, progress between a and b)
preview → editor:
find the pair bracketing the webview's scrollTop
reveal the interpolated source line with revealRange(..., AtTop)
Timing constants are pinned, because scroll sync that feeds back on itself is the classic failure of this feature:
- Reparse debounce after the last keystroke: 150 ms.
- Preview → editor scroll notification throttle: 50 ms.
- Echo lockout: after applying a scroll from one side, ignore the other side's scroll events for 250 ms.
- Editor → preview scrolling is applied on the next animation frame, unthrottled; it is the direction the user perceives as latency.
Click-through:
- A click on
a.hmd-linkopensdata-hmd-path, revealing the heading or block addressed bydata-hmd-fragmentwhere present (VSX-020). - A double-click, or a click on any element not inside a link, reveals that
element's
data-linein the source editor (VSX-021). - Navigation MUST preserve the pin state: following a link from a pinned preview moves the editor, not the preview.
7. Diagnostics¶
- The extension owns one
DiagnosticCollectionnamedHyperMarkDown. - Diagnostics come from
@hypermarkdown/coreand are mapped one-for-one: rule ID intoDiagnostic.code, HMD-0001 severity intoDiagnosticSeverity.Erroror.Warning, and the 1-indexed(line, column)span into a zero-indexedRange. diagnostics.scope: "workspace"publishes for every card in the index;"open"publishes only for open documents. The default is"workspace", because a red link in a card nobody has opened is exactly the work item the format wants surfaced (sketch 28).- Diagnostics for a document MUST be recomputed from the in-memory buffer, so the Problems panel and the preview never disagree about unsaved text.
8. The vault catalog, the index, and watching¶
A vault is a directory tree that carries its own .hmd/, together with the
namespace root its wiki setting names. One folder open in the editor may hold
several, and they nest: this repository holds doc/wiki plus one vault per
example tree. The host therefore holds a catalog of vaults rather than one
project, and a card is named by the vault that claims it as well as by its path
— the same path means different cards in two vaults.
- Discovery is per card, not per window. From the card's own URI the host
walks up through its parents to the nearest directory carrying
.hmd/; that directory is the card's project root, andwikiresolved against it is the namespace root, exactly as HMD-0020 §5 defines. This is what the canonical implementation'sfind_project_roothas always done, so the two agree about what "the project" is for a file on disk. - The walk stops at the containing workspace folder. An editor may not read
arbitrary ancestors of the user's disk, and the
.gitfallback the CLI uses is a convenience a workspace folder already provides. It stops at the containing folder, not the first one, so a multi-root workspace resolves each card against its own folder. - A marker that does not claim the card does not stop the walk. A
.hmd/whosewikiresolves to a tree the card is not in is some other vault's marker; the walk continues above it. - The folder itself is the vault of last resort. With no
.hmd/anywhere above the card, the workspace folder's own root applies —hyperMarkdown.rootwhen set, elsewikifrom the folder's.hmd/config.toml, elsedoc/wiki, else the folder. This is what keeps a repository with no marker at its root, and a bare directory of cards with no setup at all, working (VSX-061). An explicithyperMarkdown.rootis an instruction rather than a hint, and wins for the folder it is set in. - Vaults are built lazily and live for the window. The first card that asks
for a vault builds it; it is disposed with the window. One index per vault is
the honest model — the alternative, re-initialising a single index on every
crossing, throws the index away each time the user clicks between
doc/wikiand an example tree, and makes diagnostics, the graph, and the watcher lie about which knowledge base they describe. - Resolution never crosses a vault boundary. Two vaults are two namespaces:
a
[[wikilink]], an embed, or a graph edge that resolved into a neighbouring vault would make the same card render differently depending on what else happened to be checked out. Every path in a host/webview message is relative to the vault of the card that produced it. - Each vault owns a
FileSystemWatcheron**/*.hmdunder its own namespace root, handling create, change, and delete, including changes made outside VS Code. A change to.hmd/config.tomltriggers a full rebuild, since it can move the root. - Index updates MUST be incremental: an edit re-parses one document and re-resolves that document and its inbound neighbours (HMD-0020 §6). Rebuilding the tree on a keystroke would make VSX-051 unreachable on any real wiki.
- Budgets, as SHOULD, and per vault: a cold index of 1 000 cards within 2 s; parse, resolve, and render of a 100 KiB card within 100 ms. A window holding several vaults pays the cold cost once per vault, when a card in it is first opened.
- The empty state names the cause. A preview with no card says so; a preview
looking at an open
.hmdfile that no vault claims MUST say that instead, and say what would claim it. Telling someone to open a card while they are looking at one is accurate about the extension's state and misleading about theirs (issue 0108). diagnostics.scope: "workspace"means every card in the index, and there is one index per vault — so it publishes every vault the window has opened a card in, and grows as the user moves between them."open"is unchanged and is the setting for anyone who wants a fixed scope.
9. The graph tab¶
The graph tab draws the vault inside this webview, and replaces the backlinks tab: a list of the cards with an edge into the current one is the graph's card scope pointed at what links here, and a strip carrying both would offer the same thing twice.
What it draws, how it is bounded, and how it is drawn are specified in HMD-0025, which this record defers to. Two obligations stay here, because they are this record's own:
- The tab MUST consume the index's node and edge lists through the
graphmessage of the host/webview split above, never the per-document IR, and its view controls MUST ask the host for a new cut rather than filter a cached one. - The tab MUST draw under the policy below, which forbids remote script and
eval, so any layout library it uses is compiled into the extension's own bundle.
10. Deferred tabs¶
The mind map is not implemented. The contract it will use is fixed here so its arrival is additive:
- A tab is a module exporting
mount(container, api)and receivingrendermessages with the same envelope as the host/webview split above.
11. Webview hardening¶
- The webview is created with
enableScripts: true,localResourceRootslimited to the extension's ownmedia/directory, andretainContextWhenHiddenoff — state is rehydrated from arendermessage instead, because retaining context costs memory in every window for the whole session. - The content security policy is pinned:
default-src 'none';
img-src {webview.cspSource} data:;
font-src {webview.cspSource};
style-src {webview.cspSource} 'unsafe-inline';
script-src 'nonce-{nonce}';
default-src 'none'and the absence ofconnect-srcmean the preview cannot reach the network at all (VSX-062). KaTeX fonts are bundled locally, per HMD-0020 §3.3, precisely so this policy can hold.- A fresh nonce MUST be generated per webview load, from
crypto.getRandomValues. style-src 'unsafe-inline'is permitted only because VS Code injects theme variables as an inline style block. No extension-authored inline styles.- The renderer MUST NOT use
innerHTMLfor anything other thanHtmlBlock.html, and MUST NOT construct HTML fromdata-hmd-*values by string concatenation.
12. Packaging and CI¶
- The extension depends on the published
@hypermarkdown/coreby semver range, not on a workspace path. Development satisfies that range fromtools/hmd-ts-core/through the npm workspaces root, so a change to the core is live in the extension without a release; the manifest still names the package a consumer would install, so the extension survives the core gaining consumers outside this repository. - Bundled with esbuild into a single
dist/extension.jsplus onemedia/webview.js; nonode_modulesin the VSIX (VSX-063). The core is inlined by that bundle, so the shipped VSIX carries whichever copy the range resolved to at build time. .vscodeignoreexcludes sources, tests, and the corpus.- CI gains a
jsjob — install, typecheck, unit tests, corpus run,vsce package— running independently of the existing Python matrix so neither half can block the other's merge.
Backwards Compatibility¶
Nothing is released, and the extension is additive to the repository: it adds
tools/hmd-vsc-ext/ and a CI job, and modifies no existing file
except .github/workflows/ci.yml and the proposal index. The .hmd files
themselves are unchanged — a card that lints clean today renders unchanged
tomorrow, since every semantic decision is HMD-0020's and HMD-0020 is a port of
HMD-0001 rather than an extension of it.
Security Considerations¶
- Untrusted content in a script host. Card content reaches a webview.
Mitigations are layered: HTML escaped at parse time (HMD-0020 §3), a CSP with
default-src 'none'and a per-load script nonce (§11), and a renderer that touchesinnerHTMLonly for content the core produced. - The message boundary is a trust boundary (§4). Every inbound path is re-validated against the namespace root before any file operation, so a renderer defect cannot become an arbitrary-file-read.
- Workspace trust. The extension MUST declare limited support for untrusted workspaces: indexing and preview are read-only and safe, so they run; create-card is disabled until the workspace is trusted.
- Writes. Exactly one code path writes to the workspace — create-card (§5) —
and it goes through
WorkspaceEditso the action is undoable and visible in the editor's own history. - No telemetry. The extension collects nothing and has no network capability to send it with.
Deployment / Activation¶
- E1 — skeleton. Package,
hmdlanguage, TextMate grammar, view container and an empty webview, esbuild, the CI job. No core dependency yet; this step is verifiable by opening a.hmdfile and seeing it highlighted. - E2 — index and diagnostics. Wire
@hypermarkdown/coretovscode.workspace.fs, build the index, publish diagnostics. Proves the whole host-side stack with no rendering at all. - E3 — the rendered tab. IR delivery, the renderer, embed cards, red links, scroll sync, click-through. This is the milestone the feature exists for; everything before it is scaffolding and everything after is additive.
- E4 — backlinks, breadcrumb, create-card, pin. Backlinks was retired by E6, which subsumes it.
- E5 — packaging. VSIX, marketplace metadata, README, an animated capture of E3 doing its job.
- E6 — the graph tab, which replaces the backlinks tab.
E1 and E2 depend only on HMD-0020's parser and resolver stages, so they can start as soon as those land and before expansion exists.
Reference Implementation¶
tools/hmd-vsc-ext/
package.json contributions of §1
syntaxes/hmd.tmLanguage.json
src/
extension.ts activation, commands, disposables
workspaceHost.ts WorkspaceHost over vscode.workspace.fs, discovery
catalog.ts the vault catalog: which vault claims a URI
vault.ts one vault's index, watcher, and unsaved buffers
diagnostics.ts core diagnostics → DiagnosticCollection
preview/
view.ts WebviewViewProvider
panel.ts WebviewPanel, ViewColumn.Beside
html.ts shell HTML, CSP, nonce
graphView.ts the vault graph cut down to one view
protocol.ts message types, shared with the webview
commands/
createCard.ts
media/
webview.ts renderer entry, tab strip, scroll sync
graph.ts the graph tab: Cytoscape, toolbar, click handling
webview.css theme variables only
test/
unit/ vitest, renderer and protocol
integration/ @vscode/test-cli
Test Plan¶
Unit tests (vitest, jsdom) MUST include:
- IR → DOM: an
EmbedBlockrenders a card whose header carries the embedded card's path; a nested embed renders a nested card; an unresolved embed renders a failure card rather than nothing. - Every rendered block carries
data-linematching its IR span. - Scroll sync: the anchor interpolation is exact at anchors and monotonic between them; the echo lockout suppresses a synthetic round trip.
- Protocol: a malformed inbound message is rejected; an
openSourcefor a path outside the root is rejected. - Discovery: a folder holding a nested vault resolves a card in each to different roots, over a real directory tree rather than a stubbed one — the claim is about a tree, so faking the tree would test nothing (issue 0108).
- CSP: the generated shell HTML contains a nonce, and every
<script>carries it. - The tab strip carries the tabs this build has and no others, so a retired tab cannot survive in the shell after its renderer is gone. What the graph itself draws is covered by HMD-0025.
Integration tests (@vscode/test-cli) MUST include:
- Opening a fixture card activates the extension, populates the index, and
publishes exactly the diagnostics
hmd lintpublishes for the same tree. openPreviewToSideopens a panel rendering the same content as the view.- Editing a buffer without saving updates both the preview and the diagnostics.
- Typing an incomplete
[[and pausing leaves the preview rendered and the Problems panel silent for that line; completing the link resolves it without an intervening blank frame. - Editing a paragraph halfway down a long card leaves the preview's scroll position and every collapsed embed card exactly as they were.
- Deleting a card's target on disk turns its inbound links red without a reload.
npm ci
npm run -w tools/hmd-vsc-ext typecheck
npm run -w tools/hmd-vsc-ext test
npm run -w tools/hmd-vsc-ext package
Open Questions¶
- Should the preview render
.mdfiles found under the namespace root? HMD-0001 §4 makes them invisible to the resolver, but a user who opens one next to a card will read the empty preview as a failure. - Should an untitled or out-of-workspace
.hmdbuffer preview at all, with no namespace root to resolve against? Rendering with every link red is honest but noisy. - What is the marketplace publisher identity, and does the extension ID match the npm scope?
Changelog¶
- 2026-08-06: drafted
- 2026-08-06: §5.1 added — live editing against the unsaved buffer, keyed DOM patching, no-blank rendering, and diagnostics on a slower clock than the preview
- 2026-08-08: §3 rewritten — the preview is an editor tab, the view container is gone, several previews may be open at once, and panels are restored across a reload. Retires the secondary-side-bar Open Question (VSX-001), which the change makes moot rather than answers. See issue 0103.
- 2026-08-08: §3 corrected — the same revision had a preview pin itself to whichever card was active when it opened, which stopped it following the editor or its own links for the life of the tab. Following is now normative and unconditional, link clicks move the preview, and revealed source may not land in the preview's own column. See issue 0105.
- 2026-08-08: §3 — the pinned flag is barred from persisted state, and opening
a preview reuses the unpinned one already in the target column. The first
correction reached only previews created after it; tabs restored from
workspace storage carried
pinned: truewritten by the older build and came back frozen. See issue 0105, second round. - 2026-09-14: Packaging and CI — the core dependency is settled. The extension
depends on the published
@hypermarkdown/coreby semver range, satisfied from the workspace during development and inlined by the esbuild bundle at package time. Closes the Open Question that asked between the two. - 2026-09-14: the graph tab replaces backlinks rather than joining it — a strip carrying both would offer the same thing twice, since the retired list is the card scope pointed at what links here. The tab's own design moved out to HMD-0025, which a bundled layout engine, a node cap, and a scope model earned; what stays here is the message it arrives on and the policy it draws under. Only the mind map is still deferred. See issue 0109.
- 2026-09-14: §5 — create-card never prompts. The link's own form picks the path: bare is a sibling, absolute is under the root, relative is where it points, and the author moves the file or writes the link relative instead of answering a dialog. Closes the Open Question that asked between picking and prompting.