// TextCanvas — narrative editor surface with markup chips, headings, prose. // Renders chips per markupStyle: "pill" | "underline" | "bracket" | "color". const CHIP_TYPE_LABEL = { block: "block", property: "property", association: "assoc", requirement: "req", }; function Chip({ kind, id, label, markupStyle, onHover, onClick, focused }) { const cls = `chip chip-${kind} chip-style-${markupStyle}${focused ? " chip-focus" : ""}`; const handlers = { onMouseEnter: () => onHover && onHover(id), onMouseLeave: () => onHover && onHover(null), onClick: () => onClick && onClick(id, kind), }; if (markupStyle === "bracket") { return ( [ {CHIP_TYPE_LABEL[kind]}: {label} ] ); } if (markupStyle === "underline") { return ( {kindGlyph(kind)} {label} ); } // pill (default) and color both render as filled chips, color uses kind color, pill is muted. return ( {kindGlyph(kind)} {label} ); } function kindGlyph(kind) { switch (kind) { case "block": return "▢"; case "property": return "·"; case "association": return "→"; case "requirement": return "§"; default: return "·"; } } function TextCanvas({ data, density, markupStyle, focusBlockId, setFocusBlockId, gutter }) { const padY = density === "compact" ? 10 : 18; return (
{gutter} {data.narrative.map((node, i) => { if (node.type === "h1") return

{node.text}

; if (node.type === "h2") return

{node.text}

; if (node.type === "p") { return (

{node.children.map((c, j) => { if (c.t === "text") return {c.v}; if (c.t === "chip") return ( setFocusBlockId(id)} /> ); return null; })}

); } return null; })} {/* a Socrates inline annotation (margin note) */}
Σ Socrates · margin "Refuses to produce solutions" is a strong constraint. Have you decided what counts as a "solution" vs. a "scaffold"? This boundary will determine whether the refusal policy is enforceable.
); } window.TextCanvas = TextCanvas; window.Chip = Chip;