diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index 5adccbb7efe..cdbff4f60fd 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -2913,6 +2913,14 @@ export class CSSField extends TextAreaField { }; } +// A plain markdown StringField. BFM embed directives (`:card[…]` / `:file[…]`) +// in its content render as (correctly-sized) broken-link placeholders and do +// NOT resolve: a StringField is a bare string with no `id` to resolve relative +// refs against, no `linksToMany` storage to hold resolved instances, and no +// query to populate them at index time. Resolution is an index-time +// relationship concern — use `RichMarkdownField` (or a `.md` `MarkdownDef` +// file), which own the `linkedCards`/`linkedFiles` relationships, when embeds +// must render live. export class MarkdownField extends StringField { static displayName = 'Markdown'; static icon = MarkdownIcon; diff --git a/packages/base/default-templates/markdown.gts b/packages/base/default-templates/markdown.gts index 8a65f1c3ff2..2ae8f5be44d 100644 --- a/packages/base/default-templates/markdown.gts +++ b/packages/base/default-templates/markdown.gts @@ -10,6 +10,7 @@ import LinkOffIcon from '@cardstack/boxel-icons/link-off'; import { bfmRefFormatAndSize, + bfmResolvedEmbedStyle, buildWaiter, cardTypeName, fileNameFromUrl, @@ -271,14 +272,14 @@ export default class MarkDownTemplate extends GlimmerComponent<{ let format: CardSlotFormat = derived.format; let sizeStyle: string | undefined = derived.sizeStyle; - // Fitted slots carry an inline width/height plus `overflow: hidden` - // so the resolved instance occupies the requested footprint. - let resolvedStyle: ReturnType | undefined; - if (format === 'fitted') { - resolvedStyle = htmlSafe( - sizeStyle ? `${sizeStyle}; overflow: hidden` : 'overflow: hidden', - ); - } + // Non-atom resolved slots carry a footprint so the instance occupies + // a definite box instead of collapsing (isolated/inline-embedded + // default templates lay out at 100%). Fitted uses its requested + // dimensions; embedded/isolated get shared defaults. Same helper as + // the other render surfaces so footprints stay in lockstep. + let resolvedStyleRaw = bfmResolvedEmbedStyle(format, kind, sizeStyle); + let resolvedStyle: ReturnType | undefined = + resolvedStyleRaw ? htmlSafe(resolvedStyleRaw) : undefined; let resolvedUrl = resolveUrl(rawUrl, baseUrl); diff --git a/packages/host/app/components/markdown-embed-chooser/preview/index.gts b/packages/host/app/components/markdown-embed-chooser/preview/index.gts index 07cf88bdd32..ba2c99fba07 100644 --- a/packages/host/app/components/markdown-embed-chooser/preview/index.gts +++ b/packages/host/app/components/markdown-embed-chooser/preview/index.gts @@ -13,6 +13,7 @@ import { eq, not } from '@cardstack/boxel-ui/helpers'; import { bfmRefFormatAndSize, + bfmResolvedEmbedStyle, type BfmSizeSpec, } from '@cardstack/runtime-common/bfm-card-references'; @@ -266,38 +267,25 @@ export default class MarkdownEmbedPreview extends Component { return sizeStyle ? htmlSafe(sizeStyle) : undefined; } - // Fitted slots carry an inline width/height plus `overflow: hidden` so the - // instance occupies the requested footprint — derived through the same helper - // the live markdown renderer uses (`rendered-markdown.gts`). Inline embedded - // and isolated have no intrinsic inline width: the default template's - // `width/height: 100%` resolves against the inline-block wrapper, which is - // itself shrink-wrapping, and the box collapses. Give the wrapper a definite - // footprint that matches the live renderer's loading placeholders so the - // preview shows a real card body. + // Resolved-embed footprint, shared with the live markdown render surfaces + // (saved `MarkdownTemplate`, host preview panel, CodeMirror editor) through + // `bfmResolvedEmbedStyle`. Isolated and inline embedded have no intrinsic + // inline width — their default `width/height: 100%` resolves against a + // shrink-wrapping wrapper and the box collapses — so the helper hands back a + // definite footprint that matches the live renderers' loading placeholders. private get sizeStyle(): ReturnType | undefined { let { format } = this.args; + let fittedSizeStyle: string | undefined; if (format === 'fitted') { let { width, height } = this.args.sizeSpec ?? { format: 'fitted' }; - let { sizeStyle } = bfmRefFormatAndSize( + fittedSizeStyle = bfmRefFormatAndSize( 'fitted', width === undefined ? undefined : String(width), height === undefined ? undefined : String(height), - ); - return htmlSafe( - sizeStyle ? `${sizeStyle}; overflow: hidden` : 'overflow: hidden', - ); + ).sizeStyle; } - if ( - this.kind === 'inline' && - (format === 'embedded' || format === 'isolated') - ) { - let footprint = - format === 'isolated' - ? 'width: 24rem; height: 18.75rem' - : 'width: 16rem; height: 9.375rem'; - return htmlSafe(`${footprint}; overflow: hidden`); - } - return undefined; + let style = bfmResolvedEmbedStyle(format, this.kind, fittedSizeStyle); + return style ? htmlSafe(style) : undefined; }