From 64273827484d0a668e037e7442123ac9cc1a57c6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 22:43:26 +0900 Subject: [PATCH] feat: land the first cited evidence under the named citation next action (v2.8.0) Opening Public post from Demo Corp members now lands the first cited evidence under the citation next action, ahead of the input. Home list opens still wait for a citation click. --- .../2.8.0-land-first-cited-evidence.md | 5 ++++ CHANGELOG.md | 10 +++++++ CLAUDE.md | 4 ++- frontend/package.json | 2 +- frontend/src/App.test.tsx | 29 ++++++++++++++++++- frontend/src/App.tsx | 25 ++++++++++++---- lineageweave/__init__.py | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 9 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 CHANGELOG.d/2.8.0-land-first-cited-evidence.md diff --git a/CHANGELOG.d/2.8.0-land-first-cited-evidence.md b/CHANGELOG.d/2.8.0-land-first-cited-evidence.md new file mode 100644 index 000000000..6af53e0e6 --- /dev/null +++ b/CHANGELOG.d/2.8.0-land-first-cited-evidence.md @@ -0,0 +1,5 @@ +# 2.8.0 Land the first cited evidence under the named citation next action + +Open Public post from the landed Demo Corp members and the first +cited evidence sits under the citation next action, ahead of the +input. Home list opens still wait for a citation click. diff --git a/CHANGELOG.md b/CHANGELOG.md index ca7bfb41f..a8bb0771a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.8.0] - 2026-08-17 + +### Added + +- Opening Public post from the landed Demo Corp members now puts the + first cited evidence immediately under the named citation next + action, ahead of the chat input. Home list opens still wait for a + citation click. No TEPP theta is invented. No cutoff body is + invented (ADR 0016). + ## [2.7.2] - 2026-08-17 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index 05cc36284..1bcf50763 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -65,6 +65,8 @@ related nodes. After those related nodes land, the popup names Ask about this lineage as the next read. After that next action, the popup lands Ask about this lineage. After landed chat, the popup names the first Ask. After that next action, the popup lands the first Ask -answer. Changing the week first still +answer. After landed first Ask answer, the popup names the first +cited source. After that next action, the popup lands the first cited +evidence. Changing the week first still focuses the report period field. Mean θ stays on the period-report panel. diff --git a/frontend/package.json b/frontend/package.json index d487877e1..641d14d81 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.7.2", + "version": "2.8.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index c972280dd..16891e3bb 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -1504,6 +1504,7 @@ describe("App, authenticated", () => { expect(screen.queryByRole("status", { name: "Ask next action" })).not.toBeInTheDocument(); expect(screen.queryByRole("status", { name: "Ask seed next action" })).not.toBeInTheDocument(); expect(screen.queryByRole("status", { name: "Ask citation next action" })).not.toBeInTheDocument(); + expect(screen.queryByRole("complementary", { name: "Evidence" })).not.toBeInTheDocument(); expect(screen.queryByText("Related to Ada West")).not.toBeInTheDocument(); expect(screen.queryByText("Related to Priya Nair")).not.toBeInTheDocument(); const popup = document.querySelector(".popup-panel"); @@ -1547,6 +1548,10 @@ describe("App, authenticated", () => { expect(homeInput.compareDocumentPosition(homeAnswer) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe( 0, ); + expect(screen.queryByRole("complementary", { name: "Evidence" })).not.toBeInTheDocument(); + expect( + screen.queryByText("The evidence panel should show exactly this text."), + ).not.toBeInTheDocument(); expect(screen.getByRole("button", { name: /ask seeded question: what happened between these events/i })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /ask seeded question: who is involved/i })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /ask seeded question: what is the next commitment/i })).toBeInTheDocument(); @@ -2462,6 +2467,17 @@ describe("App, authenticated", () => { expect( within(popup as HTMLElement).getByRole("button", { name: "Open evidence: Linked post" }), ).toHaveAttribute("aria-current", "true"); + const citedEvidence = await screen.findByRole("complementary", { name: "Evidence" }); + expect(await within(citedEvidence).findByText("Linked post")).toBeInTheDocument(); + expect( + await within(citedEvidence).findByText("The evidence panel should show exactly this text."), + ).toBeInTheDocument(); + expect( + citedNext.compareDocumentPosition(citedEvidence) & Node.DOCUMENT_POSITION_FOLLOWING, + ).not.toBe(0); + expect( + citedEvidence.compareDocumentPosition(askInput) & Node.DOCUMENT_POSITION_FOLLOWING, + ).not.toBe(0); await waitFor(() => expect(document.getElementById("post-ask")).toHaveFocus()); } finally { HTMLElement.prototype.scrollIntoView = originalScrollIntoView; @@ -2820,9 +2836,20 @@ describe("App, authenticated", () => { expect(firstAskAnswer.compareDocumentPosition(affiliate) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe( 0, ); - expect(await screen.findByRole("status", { name: "Ask citation next action" })).toHaveTextContent( + const citedNext = await screen.findByRole("status", { name: "Ask citation next action" }); + expect(citedNext).toHaveTextContent( "Linked post is the first cited source. Open that evidence next.", ); + const citedEvidence = await screen.findByRole("complementary", { name: "Evidence" }); + expect( + await within(citedEvidence).findByText("The evidence panel should show exactly this text."), + ).toBeInTheDocument(); + expect( + citedNext.compareDocumentPosition(citedEvidence) & Node.DOCUMENT_POSITION_FOLLOWING, + ).not.toBe(0); + expect( + citedEvidence.compareDocumentPosition(affiliate) & Node.DOCUMENT_POSITION_FOLLOWING, + ).not.toBe(0); await waitFor(() => expect(document.getElementById("post-ask")).toHaveFocus()); }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 046d6796c..55d27c59f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -107,7 +107,7 @@ function EvidencePanel({ }: { postId: string; accessToken: string; - onClose: () => void; + onClose?: () => void; }) { const [post, setPost] = useState(null); @@ -118,7 +118,7 @@ function EvidencePanel({ return (
- + {onClose ? : null}

Evidence

{!post &&

Loading source post...

} {post && ( @@ -183,6 +183,7 @@ function ChatPanel({ setAnswer(null); setError(null); setSeededOnly(false); + setEvidencePostId(null); fetchPostChat(accessToken, postId) .then((history) => setExchanges(history.exchanges)) .catch(() => setExchanges([])); @@ -214,9 +215,12 @@ function ChatPanel({ } } + const firstCitedPostId = + exchanges[0]?.cited_posts?.[0]?.post_id ?? exchanges[0]?.cited_post_ids[0] ?? null; const firstCitedTitle = exchanges[0]?.cited_posts?.[0]?.post_title ?? - (exchanges[0]?.cited_post_ids[0] ? exchanges[0].cited_post_ids[0].slice(0, 8) : null); + (firstCitedPostId ? firstCitedPostId.slice(0, 8) : null); + const landedEvidencePostId = nameFirstAsk ? (evidencePostId ?? firstCitedPostId) : null; return (
@@ -247,6 +251,17 @@ function ChatPanel({ {firstCitedNextAction(firstCitedTitle)}

) : null} + {nameFirstAsk && landedEvidencePostId ? ( + setEvidencePostId(null) + : undefined + } + /> + ) : null} {!seededOnly && (
)} - {evidencePostId && ( + {!nameFirstAsk && evidencePostId ? ( setEvidencePostId(null)} /> - )} + ) : null}
); } diff --git a/lineageweave/__init__.py b/lineageweave/__init__.py index e0cb19a4a..feb27cbf7 100644 --- a/lineageweave/__init__.py +++ b/lineageweave/__init__.py @@ -55,4 +55,4 @@ "sentence_excerpts", ] -__version__ = "2.7.2" +__version__ = "2.8.0" diff --git a/pyproject.toml b/pyproject.toml index 9516fb999..9ad326be4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.7.2" +version = "2.8.0" description = "Reconstructs git-branch-style lineage DAGs from scattered short records using multi-channel score fusion and LLM adjudication." readme = "README.md" license = { text = "MIT" } diff --git a/uv.lock b/uv.lock index 5d0176af9..fd22d9c95 100644 --- a/uv.lock +++ b/uv.lock @@ -454,7 +454,7 @@ wheels = [ [[package]] name = "lineageweave" -version = "2.7.2" +version = "2.8.0" source = { virtual = "." } dependencies = [ { name = "certifi" },