feat(showcase_ui): support verification and finding cards in agent notes
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user