feat(showcase_ui): support verification and finding cards in agent notes

This commit is contained in:
somew1nd committed 2026-09-02 00:58:48 -07:00
1 parent 99343dfe55
commit 73d7b0c116
7 files changed
+494 -52

No files matched your search

@@ -0,0 +1,70 @@
import { parseNoteLines, parseNote, renderMarkdownToHtml } from './markdown-parser.util';
describe('markdown-parser.util verification & check lines', () => {
it('should parse - verify: line as type verify with checkKind verify', () => {
const markdown = `- [x] Parent task
- [x] Subtask 1
- verify: Commute duration is recorded in note \`eta_details\``;
const lines = parseNoteLines(markdown);
expect(lines.length).toBe(3);
expect(lines[0].type).toBe('checked');
expect(lines[1].type).toBe('checked');
const verifyLine = lines[2];
expect(verifyLine.type).toBe('verify');
expect(verifyLine.checkKind).toBe('verify');
expect(verifyLine.atEnd).toBe(false);
expect(verifyLine.segments.map(s => s.text).join('')).toBe('Commute duration is recorded in note eta_details');
expect(verifyLine.segments.find(s => s.code)?.text).toBe('eta_details');
});
it('should parse - assert: and - assert@end: lines', () => {
const markdown = `- [ ] Open app
- assert: the welcome screen shows up
- assert@end: final status is completed`;
const lines = parseNoteLines(markdown);
expect(lines.length).toBe(3);
expect(lines[1].type).toBe('verify');
expect(lines[1].checkKind).toBe('assert');
expect(lines[1].atEnd).toBe(false);
expect(lines[2].type).toBe('verify');
expect(lines[2].checkKind).toBe('assert');
expect(lines[2].atEnd).toBe(true);
});
it('should parse - finding: lines', () => {
const markdown = `- [x] Step 1
- finding: Unresolved verify failure`;
const lines = parseNoteLines(markdown);
expect(lines[1].type).toBe('finding');
expect(lines[1].checkKind).toBe('finding');
expect(lines[1].segments[0].text).toBe('Unresolved verify failure');
});
it('should group verify lines under parent milestone checks in parseNote', () => {
const note = `- [x] Open Maps
- [x] Search destination
- verify: Arrival time is visible in note \`eta\``;
const parsed = parseNote(note);
expect(parsed.milestones.length).toBe(1);
expect(parsed.milestones[0].subSteps.length).toBe(1);
expect(parsed.milestones[0].subSteps[0].type).toBe('checked');
expect(parsed.milestones[0].checks.length).toBe(1);
expect(parsed.milestones[0].checks[0].type).toBe('verify');
expect(parsed.milestones[0].checks[0].checkKind).toBe('verify');
});
it('should render verify badges in renderMarkdownToHtml', () => {
const text = `- verify: status is ok`;
const html = renderMarkdownToHtml(text);
expect(html).toContain('class="md-verify-item"');
expect(html).toContain('<span class="verify-badge">verify</span>');
expect(html).toContain('status is ok');
});
});
@@ -72,6 +72,31 @@ export function parseNoteLines(content: string): MarkdownLine[] {
const rawText = trimmed.substring(6);
return { type: 'unchecked', segments: parseMarkdownSegments(rawText), indent };
}
// Check lines (- verify: / - assert: / - verify@end: / - assert@end:)
const checkMatch = trimmed.match(/^[-*]\s*(verify|assert)(@end)?\s*:\s*(.*)$/i);
if (checkMatch) {
const kind = checkMatch[1].toLowerCase() as 'verify' | 'assert';
const atEnd = Boolean(checkMatch[2]);
const rawText = checkMatch[3].trim();
return {
type: 'verify',
checkKind: kind,
atEnd,
segments: parseMarkdownSegments(rawText),
indent
};
}
// System findings (- finding:)
const findingMatch = trimmed.match(/^[-*]\s*finding\s*:\s*(.*)$/i);
if (findingMatch) {
const rawText = findingMatch[1].trim();
return {
type: 'finding',
checkKind: 'finding',
segments: parseMarkdownSegments(rawText),
indent
};
}
// Regular list item
if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) {
const rawText = trimmed.substring(2);
@@ -125,12 +150,17 @@ export function parseNote(content: string): ParsedNote {
type: line.type as 'checked' | 'progress' | 'unchecked',
segments: line.segments,
subSteps: [],
checks: [],
_indent: line.indent
};
milestones.push(currentMilestone);
} else if (currentMilestone) {
if (line.indent > (currentMilestone._indent ?? 0)) {
currentMilestone.subSteps.push(line);
if (line.type === 'verify' || line.type === 'assert' || line.type === 'finding') {
currentMilestone.checks.push(line);
} else {
currentMilestone.subSteps.push(line);
}
} else {
otherLines.push(line);
}
@@ -297,8 +327,22 @@ export function renderMarkdownToHtml(text: string): string {
const subLevel = Math.max(0, Math.floor((spaces - 2) / 2));
const indent = subLevel * 12;
const style = indent ? ` style="margin-left: ${indent}px"` : '';
const itemContent = formatInlineMarkdown(ulMatch[3]);
result.push(`<li${style}>${itemContent}</li>`);
const itemText = ulMatch[3];
const checkMatch = itemText.match(/^(verify|assert)(@end)?\s*:\s*(.*)$/i);
const findingMatch = itemText.match(/^finding\s*:\s*(.*)$/i);
if (checkMatch) {
const kind = checkMatch[1].toLowerCase();
const atEnd = checkMatch[2] ? '@end' : '';
const assertClass = kind === 'assert' ? ' assert' : '';
const itemContent = formatInlineMarkdown(checkMatch[3].trim());
result.push(`<li${style} class="md-verify-item"><span class="verify-badge${assertClass}">${kind}${atEnd}</span>${itemContent}</li>`);
} else if (findingMatch) {
const itemContent = formatInlineMarkdown(findingMatch[1].trim());
result.push(`<li${style} class="md-finding-item"><span class="finding-badge">finding</span>${itemContent}</li>`);
} else {
const itemContent = formatInlineMarkdown(itemText);
result.push(`<li${style}>${itemContent}</li>`);
}
continue;
}