mirror of
https://github.com/echore/vault-autopilot.git
synced 2026-07-22 08:34:00 +00:00
fix: preserve user-added headings during clip merge
Unrecognized ## headings no longer fall back to kind='motion' (which rewrote the heading text and re-sorted the section). Unknown sections keep their exact text and sort after all known sections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0a0eeaf066
commit
7f5d9f15cc
2 changed files with 24 additions and 4 deletions
|
|
@ -131,7 +131,7 @@ export function ensurePublished(content: string, published?: string | null): str
|
|||
return `${fm}\npublished: ${published}${content.slice(end)}`;
|
||||
}
|
||||
|
||||
interface ParsedSection { kind: SectionKind; startSeconds: number; text: string; }
|
||||
interface ParsedSection { kind: SectionKind | null; startSeconds: number; text: string; }
|
||||
|
||||
function kindOf(heading: string): SectionKind | null {
|
||||
// Substring match so an emoji prefix (## 🎬 内容) still resolves to its kind.
|
||||
|
|
@ -185,7 +185,7 @@ function parseSections(body: string): { head: string; sections: ParsedSection[]
|
|||
let curHeading = '';
|
||||
const flush = () => {
|
||||
if (cur) {
|
||||
const kind = kindOf(curHeading) ?? 'motion';
|
||||
const kind = kindOf(curHeading);
|
||||
const m = curHeading.match(/(\d+)s/);
|
||||
sections.push({ kind, startSeconds: m ? parseInt(m[1], 10) : 0, text: cur.join('\n') });
|
||||
}
|
||||
|
|
@ -243,15 +243,20 @@ export function mergeSection(existing: string, section: NewSection): { content:
|
|||
}
|
||||
|
||||
const incoming: ParsedSection = { kind: section.kind, startSeconds: section.startSeconds, text: section.text };
|
||||
// Unknown-kind sections (a user's hand-written heading) sort after every
|
||||
// known kind, keeping their relative order via the stable sort below.
|
||||
const rank = (k: SectionKind | null) => (k === null ? KINDS.length : KINDS.indexOf(k));
|
||||
const all = [...sections, incoming].sort((a, b) =>
|
||||
KINDS.indexOf(a.kind) - KINDS.indexOf(b.kind) || a.startSeconds - b.startSeconds,
|
||||
rank(a.kind) - rank(b.kind) || (a.kind === null || b.kind === null ? 0 : a.startSeconds - b.startSeconds),
|
||||
);
|
||||
const ordered = renumber(all);
|
||||
|
||||
const newFrontmatter = addDimension(frontmatter, section.kind);
|
||||
const dims = KINDS.filter((k) => ordered.some((s) => s.kind === k));
|
||||
const newHead = syncOverview(head, newFrontmatter, dims).replace(/\s+$/, '');
|
||||
const renderedSections = ordered.map((s) => stripTrailingRule(emojiHeading(s.text, s.kind))).join('\n\n---\n\n');
|
||||
const renderedSections = ordered
|
||||
.map((s) => stripTrailingRule(s.kind === null ? s.text : emojiHeading(s.text, s.kind)))
|
||||
.join('\n\n---\n\n');
|
||||
const newBody = [newHead, '', renderedSections, ''].join('\n');
|
||||
return { content: `${newFrontmatter}${newBody}`, skipped: false };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,21 @@ test('a ## line inside a user code fence is NOT treated as a section', () => {
|
|||
expect(r.content).toContain('## ✨ 动效 ② · 130s–138s');
|
||||
});
|
||||
|
||||
test('a hand-written unknown heading survives a merge untouched, placed after known sections', () => {
|
||||
let c = buildAnchor(meta);
|
||||
c = mergeSection(c, keyframeSection({ url: meta.videoUrl, platform: 'youtube', start: 45, end: 52, frameNames: ['k.png'] })).content;
|
||||
c = c + '\n\n---\n\n## 我的分析\n\n这是我的想法\n';
|
||||
const r = mergeSection(c, keyframeSection({ url: meta.videoUrl, platform: 'youtube', start: 130, end: 138, frameNames: ['k2.png'] }));
|
||||
expect(r.content).toContain('## 我的分析');
|
||||
expect(r.content).toContain('这是我的想法');
|
||||
expect(r.content.indexOf('## 我的分析')).toBeGreaterThan(r.content.lastIndexOf('## ✨ 动效'));
|
||||
|
||||
const r2 = mergeSection(r.content, screenshotSection(['s.png']));
|
||||
expect((r2.content.match(/## 我的分析/g) || []).length).toBe(1);
|
||||
expect(r2.content).toContain('这是我的想法');
|
||||
expect(r2.content.indexOf('## 我的分析')).toBeGreaterThan(r2.content.lastIndexOf('## 📸 截图'));
|
||||
});
|
||||
|
||||
test('hook section embeds the whole video from start (no end) + frames + 字幕', () => {
|
||||
const s = hookSection({ url: meta.videoUrl, platform: 'youtube', endSeconds: 15, frameNames: ['f1.png'], transcript: 'hello' });
|
||||
expect(s.kind).toBe('content');
|
||||
|
|
|
|||
Loading…
Reference in a new issue