Bold renders the real element: the Bold color setting applies

The owner's repro: the appearance Bold color never applied anywhere.
Root cause: streamdown's default `strong` component renders bold as
<span class="font-semibold" data-streamdown="strong"> - NOT a
<strong> element - while the app's ink plumbing (.lh-prose
:where(strong, b) in globals.css, zero specificity by design) matches
strong/b ELEMENTS. So bold text never took --lh-ink-strong: not the
factory #c96b4a, not any settings boldColor (it rendered plain
foreground at weight 600). Italic always worked because streamdown
leaves `em` native - hence the asymmetry the owner noticed.

Fix: a `strong` override in mdComponents (the stable module-level set,
so Streamdown's memoized tree keeps identity) renders the semantic
element: <strong data-streamdown="strong">. Screen readers announce
emphasis again, the designed token rule applies unmodified, and the
weight reads 700 (the .lh-prose rule) instead of the span's 600.

Both Streamdown surfaces (MessageResponse, reasoning) pass
mdComponents, so every markdown surface is covered.

Verified live: bold in seedplan now renders #c96b4a at weight 700 in a
real <strong>; after PUTting boldColor #7dd3fc the live strong flips
to rgb(125,211,252) with no reload; italic unchanged (#6fa9d6 native
em). Scratch record restored to unset.
This commit is contained in:
amosblomqvist
2026-09-23 21:55:51 +03:00
parent 10be308ab6
commit fb72b4fee3
2 changed files with 46 additions and 0 deletions
+18
View File
@@ -338,8 +338,26 @@ export function MathDisplay(props: ComponentProps<"span"> & ExtraProps) {
// ─── The stable override set (module identity: Streamdown is memoized) ────────
/** Bold as the REAL element (appearance fix, 2026-09-23): streamdown's
* default `strong` renders `<span class="font-semibold" data-streamdown=
* "strong">` — and `.lh-prose :where(strong, b)` matches strong/b
* ELEMENTS, so neither the factory ink (#c96b4a) nor a settings
* boldColor ever landed on bold text (italic worked: streamdown leaves
* `em` native). Rendering the semantic element restores screen-reader
* emphasis AND lets the designed token rule apply unmodified — color
* --lh-ink-strong, weight 700 (beating the old 600 utility). */
export function MarkdownStrong(props: ComponentProps<"strong"> & ExtraProps) {
const { children, className, node, ...rest } = props;
return (
<strong data-streamdown="strong" className={className} {...rest}>
{children}
</strong>
);
}
export const mdComponents = {
code: MarkdownCode,
table: MarkdownTable,
span: MathDisplay,
strong: MarkdownStrong,
};
+28
View File
@@ -212,3 +212,31 @@ describe("render — mermaid", () => {
assert.match(html, />Retry</);
});
});
describe("render — bold is the real <strong> (the dead Bold-color fix)", () => {
// Streamdown's default `strong` renders <span class="font-semibold"
// data-streamdown="strong"> — and the app's ink token rule
// (.lh-prose :where(strong, b), globals.css) matches strong/b
// ELEMENTS, so bold never took the --lh-ink-strong color (neither the
// factory #c96b4a nor a settings boldColor). The mdComponents override
// renders the semantic element.
it("bold renders a real <strong> element inside .lh-prose", () => {
const html = render("plain **bold** plain");
assert.ok(
html.includes('<strong data-streamdown="strong">bold</strong>'),
"bold renders as a semantic strong element",
);
assert.ok(html.includes("lh-prose"), "the markdown surface carries .lh-prose (the token rule's anchor)");
});
it("no span-for-bold regression: the font-semibold span is gone", () => {
const html = render("plain **bold** plain");
assert.ok(!html.includes('class="font-semibold"'), "streamdown's span/600 default must not render bold");
});
it("italic stays native <em> (the case that always worked)", () => {
const html = render("plain *emph* plain");
assert.ok(html.includes("<em>"), "italic renders a native em element");
});
});