fix(v0211): importNote 가 rebuildFtsTagsForNote 호출 (final review fix)
final code review 발견: F5 import path 가 note_tags INSERT 후 notes_fts.tags 갱신 안 해서 import 한 노트의 tag 가 keyword 검색에서 매칭 안 되는 회귀. Cut C 의 importNote capture revision 누락 패턴과 동일 — single write path 정책 (Cut D 도입) 의 강제 검사 누락. importNote transaction 끝에서 호출하도록 fix + 회귀 test 2건 (insert path / fork path) 추가. NoteRepository 안 note_tags INSERT path 는 updateAiResult / updateUserAiFields / importNote 3곳, 셋 다 rebuildFtsTagsForNote 호출 보장 — invariant 회복.
This commit is contained in:
@@ -780,6 +780,11 @@ export class NoteRepository {
|
|||||||
* 'capture' 첫 revision INSERT (createdAt = edited_at). 미수행 시 first user
|
* 'capture' 첫 revision INSERT (createdAt = edited_at). 미수행 시 first user
|
||||||
* edit 직후 import 시점 본문이 history 에서 사라지는 회귀 (final review 발견).
|
* edit 직후 import 시점 본문이 history 에서 사라지는 회귀 (final review 발견).
|
||||||
*
|
*
|
||||||
|
* v0.2.11 Cut D — INSERT/fork 시 tags 추가 후 rebuildFtsTagsForNote(finalId)
|
||||||
|
* 호출 — m007 trigger 가 빈 tags='' 로 FTS row 만들고, note_tags INSERT 만으로는
|
||||||
|
* notes_fts.tags 갱신 안 됨. 미수행 시 import 한 노트가 tag keyword 검색에서
|
||||||
|
* 매칭 안 되는 회귀 (final review 발견).
|
||||||
|
*
|
||||||
* deletedAt merge (v0.2.3 #4, spec §8.2): source/dest 중 IS NOT NULL 우선
|
* deletedAt merge (v0.2.3 #4, spec §8.2): source/dest 중 IS NOT NULL 우선
|
||||||
* (삭제 보존). skip 케이스에서 source NN + dest NULL 일 때만 dest 갱신.
|
* (삭제 보존). skip 케이스에서 source NN + dest NULL 일 때만 dest 갱신.
|
||||||
* insert/fork 는 source 의 deletedAt 그대로 보존.
|
* insert/fork 는 source 의 deletedAt 그대로 보존.
|
||||||
@@ -850,6 +855,8 @@ export class NoteRepository {
|
|||||||
if (t.source === 'ai') linkAi.run(finalId, row.id);
|
if (t.source === 'ai') linkAi.run(finalId, row.id);
|
||||||
else linkUser.run(finalId, row.id);
|
else linkUser.run(finalId, row.id);
|
||||||
}
|
}
|
||||||
|
// v0.2.11 Cut D — note_tags 변경 후 notes_fts.tags 동기화 (single write path).
|
||||||
|
this.rebuildFtsTagsForNote(finalId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
tx();
|
tx();
|
||||||
|
|||||||
@@ -1103,4 +1103,57 @@ describe('NoteRepository — notes_fts tags sync (v0.2.11 Cut D)', () => {
|
|||||||
.get(id) as { tags: string };
|
.get(id) as { tags: string };
|
||||||
expect(row.tags.split(' ').sort()).toEqual(['new1', 'new2']);
|
expect(row.tags.split(' ').sort()).toEqual(['new1', 'new2']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('importNote insert path: notes_fts.tags 가 csv 로 sync (final review fix)', () => {
|
||||||
|
const repo = new NoteRepository(db);
|
||||||
|
const r = repo.importNote({
|
||||||
|
id: '00000000-0000-0000-0000-000000000010',
|
||||||
|
rawText: 'imported with tags',
|
||||||
|
createdAt: '2026-04-01T00:00:00Z',
|
||||||
|
updatedAt: '2026-04-01T00:00:00Z',
|
||||||
|
aiTitle: 'imported title',
|
||||||
|
aiSummary: 'imported summary',
|
||||||
|
titleEditedByUser: false,
|
||||||
|
summaryEditedByUser: false,
|
||||||
|
aiProvider: 'p',
|
||||||
|
aiGeneratedAt: '2026-04-01T00:00:00Z',
|
||||||
|
userIntent: null,
|
||||||
|
intentPromptedAt: null,
|
||||||
|
tags: [
|
||||||
|
{ name: '기획', source: 'ai' },
|
||||||
|
{ name: '회의', source: 'user' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
expect(r.status).toBe('inserted');
|
||||||
|
const row = db
|
||||||
|
.prepare(`SELECT tags FROM notes_fts WHERE note_id=?`)
|
||||||
|
.get(r.id) as { tags: string };
|
||||||
|
expect(row.tags.split(' ').sort()).toEqual(['기획', '회의']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('importNote fork path: forked 노트의 notes_fts.tags 동기 (final review fix)', () => {
|
||||||
|
const repo = new NoteRepository(db);
|
||||||
|
const existing = repo.create({ rawText: 'v1' });
|
||||||
|
const r = repo.importNote({
|
||||||
|
id: existing.id,
|
||||||
|
rawText: 'imported v2 with tags',
|
||||||
|
createdAt: '2026-04-01T00:00:00Z',
|
||||||
|
updatedAt: '2026-04-01T00:00:00Z',
|
||||||
|
aiTitle: null,
|
||||||
|
aiSummary: null,
|
||||||
|
titleEditedByUser: false,
|
||||||
|
summaryEditedByUser: false,
|
||||||
|
aiProvider: null,
|
||||||
|
aiGeneratedAt: null,
|
||||||
|
userIntent: null,
|
||||||
|
intentPromptedAt: null,
|
||||||
|
tags: [{ name: '결재', source: 'user' }]
|
||||||
|
});
|
||||||
|
expect(r.status).toBe('forked');
|
||||||
|
expect(r.id).not.toBe(existing.id);
|
||||||
|
const row = db
|
||||||
|
.prepare(`SELECT tags FROM notes_fts WHERE note_id=?`)
|
||||||
|
.get(r.id) as { tags: string };
|
||||||
|
expect(row.tags).toBe('결재');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user