feat: add multi-fact supporting facts citation support#449
Conversation
Adds hierarchical citation model where a parent citation can have supporting facts (children) that are independently verified. This enables compound claims like "preserve and segregate all output log data" + "May 13, 2025" to be grouped under a single inline marker with worst-child-wins status propagation. Phase 1 — Types and utilities: - SupportingFact type and CitationBase.supportingFacts field - getChildCitationKey() for deterministic child keys - supportingFactExpansion.ts: client-side flatten/reassemble for verification pipeline (zero server changes) - worstChildStatus.ts: computeCompositeStatus with priority chain isMiss > isPending > isPartialMatch > isVerified Phase 2 — Parser: - Compact key c → children in COMPACT_KEY_MAP - expandCompactKeys() recursively expands children array - citationDataToCitation() maps children → supportingFacts - CompactCitationData.c and CitationData.children fields Phase 3 — Prompts: - Supporting facts instructions added to CITATION_PROMPT, COMPACT_CITATION_PROMPT, and COMPACT_CITATION_SCENARIO2_PROMPT Includes 30+ new tests and 5 example scenarios (legal cross-page, financial same-page, cross-document contract, medical, URL).
UI integration for multi-fact citations (Phase 4): - CitationComponentProps: add supportingFactVerifications and parentInstanceId props - Status badge uses computeCompositeStatus (worst-child-wins) when supportingFactVerifications is present - Parent popover stays open when child opens: parentInstanceId suppresses announceActivePopover and subscription - SupportingFactsPills: renders each supporting fact as a CitationComponent variant="chip" with indicatorVariant="dot" and popoverPortalToBody for correct layering - DefaultPopoverContent: pills section between Zone 2 (claim) and Zone 3 (evidence) - i18n: "Supporting facts" key in en/es/fr/vi - Public exports: getChildCitationKey, expandSupportingFactsForVerification, getSupportingFactVerifications, childCitationFromFact, computeCompositeStatus, SupportingFact, ParentChildKeyMap
- Remove unused getCitationKey import from supportingFactExpansion.ts - Merge duplicate type imports from citation.ts - Replace non-null assertions with optional chaining in tests - Fix biome formatting across new files - Add type narrowing guard for supportingFacts in DefaultPopoverContent
- Use fact.childIndex instead of array index for verification lookup in SupportingFactsPills (prevents mismatch if facts are filtered) - Strip nested children from child objects in parser to prevent garbage propagation from malformed LLM output - Fix biome formatting in citationParser
|
The latest updates on your projects. Learn more about Vercel for GitHub. 4 Skipped Deployments
|
✅ Playwright Test ReportStatus: Tests passed 📊 Download Report & Snapshots (see Artifacts section) What's in the Visual SnapshotsThe gallery includes visual snapshots for:
Run ID: 25302116090 |
Code Review — PR #449: Multi-fact supporting facts citation supportGreat feature addition overall. The architecture is well thought out — zero server changes, clean circular-dependency break via Bugs / Correctness1.
if (hasPartial) return { isVerified: true, isMiss: false, isPartialMatch: true, isPending: false };The PR description says the priority is 2.
} as Citation;The spread of 3. AV citations silently receive
...(supportingFacts && { supportingFacts }),
} as AudioVideoCitation;The 4.
Performance5.
export const SupportingFactsPills = memo(function SupportingFactsPills({ ... }) {
const childCitations = useMemo(
() => supportingFacts.map(fact => childCitationFromFact(fact, parentCitation)),
[supportingFacts, parentCitation],
);
// ...
});Naming / API Clarity6.
These refer to the same value but have different names across 3 files. Pick one and use it consistently. Given the existing prop is on the public 7. "Recursively" comment in
// Recursively expand children array — each child uses the same compact keysThis is only one level deep by design (children of children are stripped). The comment should say "one-level expansion" or "shallow expansion" to avoid maintainers expecting full recursion. Test Gaps8. No component tests for The PR adds 30 new unit tests for utility functions but nothing tests the rendered pills: that child 9. Missing edge case: computeCompositeStatus({ status: "found_source_match_only" }, [{ status: "found" }])Should return Minor
Summary
The core logic is sound. Addressing the |
- rename parentCitationInstanceId → parentInstanceId (shorter, clearer) - wrap SupportingFactsPills in memo; hoist child Citation objects with useMemo - add timestamps passthrough for AV child citations in childCitationFromFact - remove unused AudioVideoCitation import in citationParser; drop unsafe cast - remove unnecessary useMemo for parentKey (getCitationKey is pure and cheap) - add JSDoc invariant notes in supportingFactExpansion and worstChildStatus - add SupportingFactsPills.test.tsx covering pill rendering and prop wiring - add computeCompositeStatus test for partial+verified edge case
…ment clarity - supportingFacts.test.ts: was importing from vitest which is not installed; switch to bun:test so tests actually execute in CI - SupportingFactsPills.test.tsx: rename overspecified test name to match what is actually asserted - worstChildStatus.ts: add comment clarifying the allVerified branch is only reachable after all higher-priority (miss/pending/partial) guards have passed
Summary
childrenarray (compact keyc), prompt additions, and i18n (en, es, fr, vi)Key design decisions
SupportingFactsPills.tsxextracted to break circular dependency (Citation → DefaultPopoverContent → Citation)parentInstanceIdprop suppressesannounceActivePopoverpub/sub so parent stays open when child popover openssha1(parentKey + "|child|" + childIndex).slice(0, 16)— deterministic, collision-resistantchildrenfrom child objects to prevent garbage propagationNew public API exports
SupportingFacttype,getChildCitationKey(),childCitationFromFact()expandSupportingFactsForVerification(),getSupportingFactVerifications(),ParentChildKeyMapcomputeCompositeStatus()Test plan