Skip to content

Commit c090c05

Browse files
committed
fix(cockpit): render docs markdown blocks
1 parent 8c636e2 commit c090c05

18 files changed

Lines changed: 87 additions & 329 deletions

File tree

apps/cockpit/src/lib/render-markdown.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ describe('renderMarkdown', () => {
5151
expect(result.html).toContain('Build a streaming chat.');
5252
});
5353

54+
it('parses inline markdown inside Summary blocks', async () => {
55+
const md = '# Test\n\n<Summary>\nUse `agent()` from [`@ngaf/langgraph`](/docs/langgraph).\n</Summary>';
56+
const result = await renderMarkdown(md);
57+
expect(result.html).toContain('<code>agent()</code>');
58+
expect(result.html).toContain('<a href="/docs/langgraph">');
59+
});
60+
5461
it('renders Tip callout blocks', async () => {
5562
const md = '# Test\n\n<Tip>\nNo service layer needed.\n</Tip>';
5663
const result = await renderMarkdown(md);
@@ -89,6 +96,15 @@ describe('renderMarkdown', () => {
8996
expect(result.html).toContain('data-copy-prompt');
9097
});
9198

99+
it('renders Related blocks as markdown link lists', async () => {
100+
const md = '# Test\n\n<Related>\n- [Chat Messages](/chat/core-capabilities/messages/overview/python) - Learn how messages render\n</Related>';
101+
const result = await renderMarkdown(md);
102+
expect(result.html).toContain('doc-related');
103+
expect(result.html).toContain('<ul>');
104+
expect(result.html).toContain('<a href="/chat/core-capabilities/messages/overview/python">Chat Messages</a>');
105+
expect(result.html).not.toContain('- [Chat Messages]');
106+
});
107+
92108
it('renders ApiTable blocks as styled tables', async () => {
93109
const md = '# Test\n\n<ApiTable>\n| Signal | Type |\n|--------|------|\n| `messages()` | `BaseMessage[]` |\n</ApiTable>';
94110
const result = await renderMarkdown(md);

apps/cockpit/src/lib/render-markdown.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface ExtractedBlock {
1313
attrs: Record<string, string>;
1414
}
1515

16-
const COMPONENT_TAGS = ['Summary', 'Tip', 'Note', 'Warning', 'Prompt', 'ApiTable', 'Step', 'Steps'];
16+
const COMPONENT_TAGS = ['Summary', 'Tip', 'Note', 'Warning', 'Prompt', 'ApiTable', 'Related', 'Step', 'Steps'];
1717

1818
function extractComponentTags(source: string): { cleaned: string; blocks: ExtractedBlock[] } {
1919
const blocks: ExtractedBlock[] = [];
@@ -41,14 +41,37 @@ function extractComponentTags(source: string): { cleaned: string; blocks: Extrac
4141
return { cleaned, blocks };
4242
}
4343

44-
function renderSummary(content: string): string {
45-
return `<div class="doc-summary">${content}</div>`;
44+
async function renderInlineMarkdown(content: string): Promise<string> {
45+
return await marked.parseInline(content);
4646
}
4747

48-
function renderCallout(type: 'tip' | 'note' | 'warning', content: string): string {
48+
async function renderSummary(content: string): Promise<string> {
49+
const html = await renderInlineMarkdown(content);
50+
return `<div class="doc-summary">${html}</div>`;
51+
}
52+
53+
async function renderCallout(
54+
type: 'tip' | 'note' | 'warning',
55+
content: string,
56+
): Promise<string> {
57+
const html = await renderInlineMarkdown(content);
4958
const icons = { tip: '💡', note: '⚠️', warning: '🚨' };
5059
const labels = { tip: 'Tip', note: 'Note', warning: 'Warning' };
51-
return `<div class="doc-callout doc-callout--${type}"><div class="doc-callout__label">${icons[type]} ${labels[type]}</div><div class="doc-callout__content">${content}</div></div>`;
60+
return `<div class="doc-callout doc-callout--${type}"><div class="doc-callout__label">${icons[type]} ${labels[type]}</div><div class="doc-callout__content">${html}</div></div>`;
61+
}
62+
63+
async function renderPrompt(content: string): Promise<string> {
64+
const html = await renderInlineMarkdown(content);
65+
return `<div class="doc-prompt"><div class="doc-prompt__header"><span class="doc-prompt__label">🤖 Agentic Prompt</span><button class="doc-prompt__copy" data-copy-prompt>Copy prompt</button></div><div class="doc-prompt__content">${html}</div></div>`;
66+
}
67+
68+
async function renderRelated(content: string): Promise<string> {
69+
const html = await marked.parse(content);
70+
return `<div class="doc-related">${html}</div>`;
71+
}
72+
73+
function renderApiTable(content: string): string {
74+
return `<div class="doc-api-table">${content}</div>`;
5275
}
5376

5477
async function renderSteps(
@@ -100,14 +123,6 @@ async function parseStepContent(
100123
return html;
101124
}
102125

103-
function renderPrompt(content: string): string {
104-
return `<div class="doc-prompt"><div class="doc-prompt__header"><span class="doc-prompt__label">🤖 Agentic Prompt</span><button class="doc-prompt__copy" data-copy-prompt>Copy prompt</button></div><div class="doc-prompt__content">${content}</div></div>`;
105-
}
106-
107-
function renderApiTable(content: string): string {
108-
return `<div class="doc-api-table">${content}</div>`;
109-
}
110-
111126
function extractFilename(code: string): { filename: string | null; cleanedCode: string } {
112127
const firstLine = code.split('\n')[0];
113128
const tsMatch = firstLine?.match(/^\/\/\s*(.+\.\w+)\s*$/);
@@ -166,13 +181,14 @@ export async function renderMarkdown(source: string): Promise<RenderedMarkdown>
166181
if (!html.includes(block.placeholder)) continue;
167182
let rendered: string;
168183
switch (block.type) {
169-
case 'Summary': rendered = renderSummary(block.content); break;
170-
case 'Tip': rendered = renderCallout('tip', block.content); break;
171-
case 'Note': rendered = renderCallout('note', block.content); break;
172-
case 'Warning': rendered = renderCallout('warning', block.content); break;
184+
case 'Summary': rendered = await renderSummary(block.content); break;
185+
case 'Tip': rendered = await renderCallout('tip', block.content); break;
186+
case 'Note': rendered = await renderCallout('note', block.content); break;
187+
case 'Warning': rendered = await renderCallout('warning', block.content); break;
173188
case 'Steps': rendered = await renderSteps(block.content, blocks); break;
174189
case 'Step': rendered = ''; break;
175-
case 'Prompt': rendered = renderPrompt(block.content); break;
190+
case 'Prompt': rendered = await renderPrompt(block.content); break;
191+
case 'Related': rendered = await renderRelated(block.content); break;
176192
case 'ApiTable': {
177193
const tableHtml = await marked.parse(block.content);
178194
rendered = renderApiTable(tableHtml);

apps/cockpit/src/lib/route-resolution.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,24 @@ describe('getCapabilityPresentation', () => {
132132
});
133133
});
134134

135+
it('includes durable execution docs assets from the capability module', () => {
136+
const entry = resolveCockpitEntry({
137+
manifest: cockpitManifest,
138+
product: 'langgraph',
139+
section: 'core-capabilities',
140+
topic: 'durable-execution',
141+
page: 'overview',
142+
language: 'python',
143+
});
144+
const presentation = getCapabilityPresentation(entry);
145+
146+
expect(presentation).toMatchObject({
147+
kind: 'capability',
148+
docsPath: '/docs/langgraph/core-capabilities/durable-execution/overview/python',
149+
docsAssetPaths: ['cockpit/langgraph/durable-execution/python/docs/guide.md'],
150+
});
151+
});
152+
135153
it('presents render capabilities with module-backed metadata', () => {
136154
const entry = resolveCockpitEntry({
137155
manifest: cockpitManifest,

0 commit comments

Comments
 (0)