Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4a63f9d
Register `markdown` in the Format type and formats array
lukemelia Apr 15, 2026
ead3b01
Add whitespace-preserving render container for markdown format (CS-10…
lukemelia Apr 15, 2026
e2b7874
Prerender pipeline: capture textContent for markdown format (CS-10782)
lukemelia Apr 15, 2026
3a40dab
Add markdownEscape template helper (CS-10783)
lukemelia Apr 15, 2026
77f6001
HTML-to-markdown fallback on CardDef/FieldDef/FileDef (CS-10784)
lukemelia Apr 15, 2026
1f50fb4
Add default static markdown templates for primitive fields (CS-10785)
lukemelia Apr 15, 2026
4fc681a
Fix CS-10785 primitive markdown test expectations
lukemelia Apr 15, 2026
b8bf52c
Add explicit static markdown templates for specialized fields (CS-10786)
lukemelia Apr 15, 2026
98bab6b
Add explicit static markdown templates for domain fields (CS-10787)
lukemelia Apr 15, 2026
6ed35d0
Persist markdown format in boxel_index (CS-10796)
lukemelia Apr 15, 2026
ca0866b
Serve card markdown via HTTP endpoint (CS-10798)
lukemelia Apr 15, 2026
fe41539
Add e2e tests for markdown rendering pipeline (CS-10789)
lukemelia Apr 15, 2026
a9fd037
Fix CI test failures for markdown pipeline
lukemelia Apr 16, 2026
24d230f
Add markdown preview panel with source/rendered toggle (CS-10790, CS-…
lukemelia Apr 16, 2026
f875cb7
Add "Copy as Markdown" to isolated view context menu (CS-10794)
lukemelia Apr 16, 2026
d12111e
Use CopyCardAsMarkdownCommand with authed fetch for clipboard copy
lukemelia Apr 16, 2026
fc30f4e
Strip style and script elements from fallback markdown output
lukemelia Apr 16, 2026
584e62b
Add clipboard-copy icon and copy-card-as-markdown command to indexing…
lukemelia Apr 16, 2026
53be955
Fix broken markdown links from nested HTML elements with whitespace
lukemelia Apr 16, 2026
f8792a9
Convert embedded card containers to :card directives in markdown
lukemelia Apr 16, 2026
b69b548
Replace raw HTML rendering with RenderedMarkdown component in preview…
lukemelia Apr 16, 2026
7e807c4
Add RenderedMarkdown tests and fix CSS bleeding into embedded cards
lukemelia Apr 16, 2026
bbd8fda
Make embedded cards in markdown preview clickable for navigation
lukemelia Apr 16, 2026
ec97be2
Add Overlays to markdown format branch for click-to-navigate
lukemelia Apr 16, 2026
e7a0e73
Fix base overlay blocking clicks on card elements
lukemelia Apr 16, 2026
90732ac
Remove redundant pointer-events rule from spec-preview-overlay
lukemelia Apr 16, 2026
9bc1ddb
Add markdown format support to field playground and RatingsSummary
lukemelia Apr 16, 2026
c0b86d4
Add markdown helpers for card links and BFM card embeds (CS-10797)
lukemelia Apr 16, 2026
405ea60
Fix lint errors and update realm-indexing test expectations
lukemelia Apr 16, 2026
d3eac4b
Fix markdown-preview test templates to use valid ATX headings
lukemelia Apr 16, 2026
2de0340
Add missing destructor to LivePrerenderedSearchResource
lukemelia Apr 16, 2026
8a8d826
Add prettier-ignore for whitespace-sensitive markdown templates
lukemelia Apr 16, 2026
1f0eceb
Restore pointer-events: none on spec-preview-overlay
lukemelia Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/base/address.gts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import StringField from './string';
import CountryField from './country';
import MapPinIcon from '@cardstack/boxel-icons/map-pin';
import { EntityDisplayWithIcon } from '@cardstack/boxel-ui/components';
import { markdownEscape } from '@cardstack/boxel-ui/helpers';

function getAddressRows(
addressLine1: string | undefined,
Expand Down Expand Up @@ -92,4 +93,29 @@ export default class AddressField extends FieldDef {
};

static atom = Atom;

// CS-10786: emit the address as a CommonMark hard-break-delimited block so
// downstream consumers preserve line structure. Each logical row is
// markdown-escaped individually so e.g. a `#` in an apartment label
// doesn't become a heading.
static markdown = class Markdown extends Component<typeof AddressField> {
get text() {
let rows = getAddressRows(
this.args.model?.addressLine1,
this.args.model?.addressLine2,
this.args.model?.city,
this.args.model?.state,
this.args.model?.postalCode,
this.args.model?.country?.name,
this.args.model?.poBoxNumber,
);
if (rows.length === 0) {
return '';
}
// Two-space-then-newline = CommonMark hard break, matching the
// TextAreaField markdown convention.
return rows.map((r) => markdownEscape(r)).join(' \n');
}
<template>{{this.text}}</template>
};
}
18 changes: 18 additions & 0 deletions packages/base/base64-image.gts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
primitive,
useIndexBasedKey,
} from './card-api';
import { markdownEscape } from '@cardstack/boxel-ui/helpers';
import { tracked } from '@glimmer/tracking';
import { on } from '@ember/modifier';
import { fn } from '@ember/helper';
Expand Down Expand Up @@ -321,6 +322,23 @@ export default class Base64ImageField extends FieldDef {
</template>
};
static embedded = Base64ImageField.isolated;

// CS-10786: never emit the raw base64 payload — it bloats the document
// and is useless to downstream consumers. Emit a placeholder that names
// the alt text if available, mirroring MaybeBase64Field's behavior.
static markdown = class Markdown extends Component<typeof Base64ImageField> {
get text() {
if (!this.args.model?.base64) {
return '';
}
let alt = this.args.model?.altText;
if (alt) {
return `[binary image: ${markdownEscape(alt)}]`;
}
return '[binary image]';
}
<template>{{this.text}}</template>
};
}

// from "ember-css-url"
Expand Down
16 changes: 15 additions & 1 deletion packages/base/big-integer.gts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { primitive, Component, FieldDef } from './card-api';
import { BoxelInput } from '@cardstack/boxel-ui/components';
import { TextInputValidator } from './text-input-validator';
import { not } from '@cardstack/boxel-ui/helpers';
import { markdownEscape, not } from '@cardstack/boxel-ui/helpers';
import Number99SmallIcon from '@cardstack/boxel-icons/number-99-small';
import {
fieldSerializer,
Expand Down Expand Up @@ -64,4 +64,18 @@ export default class BigIntegerField extends FieldDef {
static embedded = View;
static atom = View;
static edit = Edit;

// CS-10786: serialize the bigint to a decimal string and escape it — the
// leading `-` of a negative value would otherwise look like a bullet
// marker at line start.
static markdown = class Markdown extends Component<typeof this> {
get text() {
let value = this.args.model;
if (value == null) {
return '';
}
return markdownEscape(BigIntegerSerializer.serialize(value));
}
<template>{{this.text}}</template>
};
}
10 changes: 10 additions & 0 deletions packages/base/boolean.gts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ export default class BooleanField extends FieldDef {
static embedded = View;
static atom = View;

// CS-10786: emit `true`/`false` literally. The primitive is already safe
// (neither `true` nor `false` is a markdown metacharacter), and `null`/
// `undefined` resolves to empty string.
static markdown = class Markdown extends Component<typeof this> {
get text() {
return this.args.model == null ? '' : String(this.args.model);
}
<template>{{this.text}}</template>
};

static edit = class Edit extends Component<typeof this> {
<template>
<div data-test-radio-group={{@fieldName}}>
Expand Down
30 changes: 30 additions & 0 deletions packages/base/brand-functional-palette.gts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GridContainer, Swatch } from '@cardstack/boxel-ui/components';
import {
buildCssVariableName,
entriesToCssRuleMap,
markdownEscape,
} from '@cardstack/boxel-ui/helpers';

import { field, contains, Component, getFields, FieldDef } from './card-api';
Expand Down Expand Up @@ -94,4 +95,33 @@ export default class BrandFunctionalPalette extends FieldDef {
}
return entriesToCssRuleMap(this.cssVariableFields);
}

// CS-10787: emit a bulleted list of the palette entries with their CSS
// color values in inline code. Skips empty slots so the output stays
// compact.
static markdown = class Markdown extends Component<
typeof BrandFunctionalPalette
> {
get text() {
let model = this.args.model;
if (!model) {
return '';
}
let rows: string[] = [];
let pairs: { key: keyof typeof model; label: string }[] = [
{ key: 'primary', label: 'Primary' },
{ key: 'secondary', label: 'Secondary' },
{ key: 'accent', label: 'Accent' },
{ key: 'light', label: 'Light' },
{ key: 'dark', label: 'Dark' },
];
for (let { key, label } of pairs) {
let value = model[key] as string | undefined;
if (!value) continue;
rows.push(`- ${markdownEscape(label)}: \`${value}\``);
}
return rows.join('\n');
}
<template>{{this.text}}</template>
};
}
64 changes: 64 additions & 0 deletions packages/base/brand-logo.gts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FieldContainer, GridContainer } from '@cardstack/boxel-ui/components';
import {
entriesToCssRuleMap,
markdownEscape,
type CssRuleMap,
} from '@cardstack/boxel-ui/helpers';

Expand All @@ -14,6 +15,7 @@ import {
getFieldDescription,
} from './card-api';
import { buildCssVariableName } from '@cardstack/boxel-ui/helpers';
import { markdownLink } from './markdown-helpers';
import {
type CssVariableField,
type CssVariableFieldEntry,
Expand Down Expand Up @@ -308,6 +310,26 @@ export class MarkField extends URLField {
</style>
</template>
};

// CS-10787: render the mark as a markdown image — the URL is the asset,
// and the alt text is empty (callers set alt via context).
static markdown = class Markdown extends Component<typeof MarkField> {
get text() {
let url = this.args.model;
if (!url) {
return '';
}
let encoded: string;
try {
encoded = encodeURI(url);
} catch {
encoded = url;
}
encoded = encoded.replace(/\(/g, '%28').replace(/\)/g, '%29');
return `![](${encoded})`;
}
<template>{{this.text}}</template>
};
}

export default class BrandLogo extends FieldDef {
Expand Down Expand Up @@ -388,4 +410,46 @@ export default class BrandLogo extends FieldDef {
}

static embedded = Embedded;

// CS-10787: emit a bulleted list of the logo URLs that are actually
// populated. Skips empty slots so the output stays compact.
static markdown = class Markdown extends Component<typeof BrandLogo> {
get text() {
let model = this.args.model;
if (!model) {
return '';
}
let rows: { label: string; url: string }[] = [];
let pairs: { key: keyof typeof model; label: string }[] = [
{ key: 'primaryMark1', label: 'Primary mark (light)' },
{ key: 'primaryMark2', label: 'Primary mark (dark)' },
{ key: 'primaryMarkGreyscale1', label: 'Primary mark greyscale (light)' },
{ key: 'primaryMarkGreyscale2', label: 'Primary mark greyscale (dark)' },
{ key: 'secondaryMark1', label: 'Secondary mark (light)' },
{ key: 'secondaryMark2', label: 'Secondary mark (dark)' },
{
key: 'secondaryMarkGreyscale1',
label: 'Secondary mark greyscale (light)',
},
{
key: 'secondaryMarkGreyscale2',
label: 'Secondary mark greyscale (dark)',
},
{ key: 'socialMediaProfileIcon', label: 'Social media icon' },
];
for (let { key, label } of pairs) {
let url = model[key] as string | undefined;
if (url) {
rows.push({ label, url });
}
}
if (!rows.length) {
return '';
}
return rows
.map(({ label, url }) => `- ${markdownEscape(label)}: ${markdownLink(url, url)}`)
.join('\n');
}
<template>{{this.text}}</template>
};
}
Loading
Loading