Obsidian Web Clipper로 페이지를 저장하다 보면 같은 페이지를 두 번 클리핑하는 일이
가끔 생긴다. 파일명으로는 잘 안 잡힌다 — 저장할 때마다 제목이 조금씩 다르게 붙기도
하고, 매번 눈으로 훑어보는 것도 지치는 일이다. 그래서 Dataview의 자바스크립트 기능을
써서, source frontmatter에 적힌 URL이 같으면 자동으로 걸러주는 스크립트를 짰다.
걸러진 게 진짜 중복이 아니었다
스크립트를 처음 돌렸는데, 결과가 이상했다. 원본 클리핑이랑 그걸 요약해서 만든 노트가
“중복”으로 같이 걸렸다. 둘은 전혀 다른 노트다 — 하나는 웹페이지를 그대로 저장한
raw 클리핑이고, 하나는 그 노트를 열어두고 채팅으로 “이거 요약해줘”라고 물어봐서 만든
결과물이다. 근데 source 필드를 열어보니 둘이 완전히 같은 URL을 갖고 있었다.
이걸 확인해보니, 원인이 예상보다 깊었다. 겉보기엔 증상 하나(URL이 같아서 오탐)였는데, 파다 보니 서로 무관한 버그 두 개가 우연히 겹쳐서 생긴 문제였다.
원인 1 — fallback 태그가 Web Clipper 태그랑 겹쳤다
채팅 답변을 새 노트로 저장하는 기능(note-exporter.ts)은, AI가 제목이랑 태그를
같이 추천해준다. 근데 이 추천이 실패하면 기본값으로 태그 하나를 붙이게 돼 있었는데,
그 기본값이 하필 "clippings"였다.
if (finalTags.size === 0) {
finalTags.add("clippings");
}
문제는 "clippings"가 Web Clipper가 원래 클리핑에 기본으로 붙이는 태그라는
거였다. 즉 AI가 태그 추천에 실패하면, 생성된 요약 노트가 진짜 웹 클리핑이랑 태그로도
구분이 안 되는 상태가 된다. 게다가 이 노트들은 나중에 raw/ 폴더로 옮겨져서 LLM
Wiki 인제스트 대상이 될 걸 감안하고 설계됐던 거라, 폴더 위치로 구분하는 것도 답이
아니었다. 결국 fallback 태그 이름 자체를 "generated"로 바꿨다 — 어디로 옮겨가든
“이건 AI가 만든 노트다”라는 정체성이 유지되게.
원인 2 — 첨부한 노트가 아니라 그 노트의 원본을 가리켰다
태그를 고치고 나니 한 겹 더 있었다. 노트를 첨부해서 질문하고 그 답을 새 노트로
저장할 때, 새 노트의 source를 정하는 로직이 이랬다.
if (originalSource && typeof originalSource === 'string') {
sourcePropertyVal = originalSource; // 첨부한 노트의 source를 그대로 복사
} else {
sourcePropertyVal = `[[${relativePathWithoutExt}]]`; // 첨부한 노트 자체를 가리킴
}
위키 노트 하나를 첨부해서 “이 노트 요약해줘”라고 물으면, 새로 생기는 노트는 그
위키 노트 자체를 출처로 남겨야 맞다. 근데 그 위키 노트의 source 필드가
문자열(단일 raw 파일 링크)이면, 그 문자열을 그대로 복사해버렸다 — 결과적으로 새
노트가 “내가 방금 요약한 그 노트”를 건너뛰고, 그 노트가 애초에 어디서 왔는지(한 단계
더 위, raw 원본)를 가리키게 됐다.
근데 이 로직을 무작정 지울 수도 없었다. 기존 테스트 하나가, raw 클리핑 자체에 진짜 외부 출처(예: 논문 URL)가 적혀 있으면 그걸 물려받는 게 맞다고 검증하고 있었고, 그건 여전히 유효한 케이스였다. 그래서 조건을 나눴다 — 외부 URL이면 물려받고, 내부 위키링크면 지금 첨부한 노트 자체를 가리키게.
const isExternalUrl = typeof originalSource === 'string' && /^https?:\/\//i.test(originalSource.trim());
if (isExternalUrl) {
sourcePropertyVal = originalSource;
} else {
const relativePathWithoutExt = meta.sourceFile.path.replace(/\.[^/.]+$/, "");
sourcePropertyVal = `[[${relativePathWithoutExt}]]`;
}
마지막으로 처음 그 스크립트도 손봤다
두 버그를 고치고 나니, 원래 짰던 Dataview 스크립트에도 한 줄이 필요해졌다. 이제
생성된 노트에는 generated 태그가 확실히 붙으니까, 중복 비교 대상에서 이 태그가
붙은 노트는 처음부터 제외하면 된다.
function isGenerated(p) {
const tags = p.tags;
if (Array.isArray(tags)) return tags.includes("generated");
return tags === "generated";
}
이렇게 해두면 raw 클리핑과 그걸 요약한 노트가 같은 URL을 공유해도(외부 URL을 물려받는 경우), 더 이상 “진짜 중복”으로 오탐되지 않는다.
남는 생각
처음엔 그냥 “중복 찾는 삽질을 줄이자”는 소박한 목표였다. 근데 그 스크립트를 돌려서 나온 이상한 결과 하나를 무시하지 않고 따라가 보니, 서로 다른 코드 경로에 있던 버그 두 개가 나왔다. 하나는 이름 하나(fallback 태그)가 다른 기능(Web Clipper)의 관례랑 우연히 겹쳐서 생긴 문제였고, 다른 하나는 “원본을 가리켜야 한다”는 규칙이 한 가지 케이스(문자열 source)에서만 조용히 다르게 동작했던 문제였다. 둘 다 각자 따로 있었다면 한참 더 늦게, 아니면 영영 못 찾았을 수도 있었다 — 중복 탐지라는 완전히 다른 목적의 도구를 만들다가 우연히 걸린 셈이다.