Markdown embed chooser modal (CS-11675/76/77)#5346
Draft
FadhlanR wants to merge 8 commits into
Draft
Conversation
A tabbed modal that wraps the existing mini choosers and the MarkdownEmbedPreviewPane from #5303. Both tabs stay mounted so each tab's search query, highlighted row, scroll position, and pane W×H survive a switch. Driven by a new `markdown-embed-chooser` service exposing `chooseCardOrFile({ defaultTab })` (edit-mode `editEmbed` follows in CS-11676). Pane teach: render with `@target` undefined so layout doesn't jump before a row is picked, CTA disabled until then. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When opened via `editEmbed({ url, refType, sizeSpec })` the matching tab
starts in `current` mode showing the placed target with Replace / Remove
buttons; the other tab still mounts its mini chooser per Zeplin 08B.
Replace flips the tab back to the chooser. Remove resolves the modal's
deferred with `{ remove: true }`. The pane gains edit-mode preload args
(initialFormat / initialWidth / initialHeight / initialKind) plus a
`ctaLabelOverride` and an `onDirtyChange` callback the parent uses to
flip the CTA between DONE and ACCEPT.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `extractBfmRefRanges(markdown)` (runtime-common) returns one `[from, to]` per BFM directive, skipping code blocks; powers in-editor range tracking and in-place replacement. - `codemirror-context.ts` `SelectionInfo` gains a `currentRef` field; the doc-wide range scan re-runs on `docChanged` and the cursor is tested against the cached ranges on every selection update. - `codemirror-editor.gts` toolbar gets a new left-most slot: an Add-embed popover (Add a card / Add a file) when the cursor is outside any directive, an Edit pencil when it's inside. Add inserts the returned BFM at the cursor (block placement gets surrounding newlines); Edit replaces the matched range, Remove deletes it. - The combined chooser modal registers a global bridge `_CARDSTACK_MARKDOWN_EMBED_CHOOSER`; `chooseMarkdownEmbed` / `editMarkdownEmbed` in runtime-common dispatch to it — mirrors the `chooseCard` / `chooseFile` pattern so the base editor reaches the host modal without a direct import. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-to-end coverage of the toolbar → modal → editor round-trip: - Toolbar surfaces an Add-embed popover (Add a card / Add a file) when the cursor is outside a directive. - Picking a card from the popover lands `:card[url]` at the cursor. - Placing the cursor inside a directive swaps the toolbar to the Edit pencil; Remove on the edit modal deletes the directive in place. Loads CodeMirrorEditor through the loader (virtual-URL module) and injects it via precompileTemplate's scope so the integration test can mount the base-realm editor alongside the host-side modal in the same render pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drives the full operator-mode flow: interact submode → open a Note card with a RichMarkdownField → switch to edit → click Add embed in the markdown toolbar → pick a card from the combined chooser modal → assert the editor's source now carries the inserted `:card[url]` directive. Second test asserts cursor-inside-directive swaps the toolbar to the Edit pencil and opens the modal on the current-target tile. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- externals.ts: shim `@cardstack/runtime-common/bfm-card-references` as
an async module so the base-realm editor (and anything else that
reaches the subpath through the loader) resolves it via the virtual
network — the loader has no way to fetch it otherwise.
- codemirror-editor.gts: drop `disabled={{not this.toolbarEnabled}}`
from the Add-embed and Edit-embed buttons; both open modals that own
their own state, so editor focus isn't a prerequisite.
- pane.gts: new `initialTargetUrl` arg. Without it, in edit mode the
dirty check briefly flips true once the resolved target instance
lands (constructor records `undefined`, then `args.target?.id` changes
to the URL), making the CTA read ACCEPT before the user edits
anything. Tab-panel passes the URL through verbatim.
- Tests: switch from `editor.cmView.view` (not a public CM6 surface) to
`cmContext.EditorView.findFromDOM(editor)`; fix one unit-test
assertion where the block-ref form needed to sit at the start of a
line for the `^::` regex to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the card/file tab control from a full-width strip into a segmented pill control (tab-pills.gts) that sits at the top of each panel's left search column, directly above its search bar. Both tabpanels stay mounted so each tab keeps its search query, highlighted row, scroll position, and the pane's size/format selection across a switch. Harmonize the two mini choosers so toggling tabs no longer jumps in scale: both now render their chrome and list rows at the shared 14px (--boxel-font-sm) size — the file tree (was 12px), the card chooser baseline, and the mini "Search Results" summary (was 16px). Each list keeps its own natural weight. Give the card chooser's search header a small bottom inset so the bar's 2px focus outline is no longer painted over by the results list below it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
288a7e6 to
d3c567d
Compare
Contributor
Preview deploymentsHost Test Results 1 files ± 0 1 suites ±0 2h 6m 5s ⏱️ + 3m 55s Results for commit 9e4f2ba. ± Comparison against earlier commit d3c567d. For more details on these errors, see this check. Realm Server Test Results 1 files ±0 1 suites ±0 8m 48s ⏱️ - 1m 24s Results for commit 9e4f2ba. ± Comparison against earlier commit d3c567d. |
Narrow the embed-chooser resolution before reading `bfm`: a negated `&&` condition doesn't narrow the union, so split the `remove` check into its own block. And read a card's display title via `cardTitle` (the base CardDef `title` field was replaced by the cardInfo-derived `cardTitle`). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Lets an author embed a card or file reference into a markdown field directly from the CodeMirror editor, choosing the target and its display size/format through a single combined chooser. Covers Linear CS-11675, CS-11676, and CS-11677.
A markdown embed reference is written as a link with an optional size/format specifier after a
|(e.g. the target URL plus a fittedWxH, oratom/embedded/isolated). The editor renders these inline, and the toolbar lets you add a new one at the cursor or edit the one under it.Changes by area
Embed-reference parsing —
runtime-commonbfm-card-references.ts— parses/serializes the size-format spec (BfmSizeSpec:atom | fitted | isolated | embedded, with optional width/height), resolves reference URLs against the doc's base, and provides amarkedtokenizer extension. Code fences and inline code spans are stripped before extraction so references inside code aren't matched.Editor integration —
base/codemirror-editor.gts+host/app/libcodemirror-context.tstracks the embed reference under the caret so the toolbar knows whether to offer Add (no ref at cursor) or Edit (ref at cursor), and which target/spec to seed.Chooser modal + service —
host/app/components/markdown-embed-chooser+ servicemarkdown-embed-chooser.tsservice is the single entry point (chooseCardOrFile/editEmbed); it holds one in-flight request and resolves aDeferredwith the picked{ refType, url, bfm }, a{ remove: true }, orundefined(cancel). A new request cancels any open one.modal.gts— the modal shell, mounted once in the operator-mode container and driven by the service via a global bridge (mirrors the existing card/file chooser pattern).tabs.gts/tab-pills.gts/tab-panel.gts— a two-tab (Cards / Files) layout. Tabs are a segmented pill control above each tab's search bar; both panels stay mounted so each keeps its search query, highlighted row, scroll position, and the pane's size/format selection across a switch.pane.gts— the right-hand preview pane: shows the selected target and lets the user choose the display format and dimensions, emitting the BFM spec on insert.Mini choosers —
card-chooser/mini,file-chooser/mini,card-searchMiniCardChooser/MiniFileChooserprimitives (card search results vs. a workspace file tree).card-search/panel-content.gts+sheet-results.gtscarry the matchingmini-variant tweaks.)Tests
markdown-embed-chooser-test.gts— end-to-end add/edit flow from the editor toolbar through the modal.markdown-embed-chooser-modal-test.gts,codemirror-embed-toolbar-test.gts,codemirror-editor-test.gts.bfm-card-references-test.ts.The chooser font-scale harmonization and focus-outline fix were verified live in the browser via the component freestyle page.
🤖 Generated with Claude Code