From ce8f65249e4be7a06b88b0f33d1a9693f54526ba Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 16:35:40 +0000 Subject: [PATCH] feat: land Event Lineage under the named evidence next action (v2.10.0) Opening Public post from landed Demo Corp members now puts Event Lineage for the cited Linked post under the evidence next action, ahead of the chat input. Home list opens keep a single Event Lineage on the opened post. No TEPP theta or cutoff body is invented. Co-authored-by: Seongho Bae --- .../2.10.0-land-evidence-event-lineage.md | 6 +++ CHANGELOG.md | 10 ++++ CLAUDE.md | 4 +- frontend/package.json | 2 +- frontend/src/App.test.tsx | 42 +++++++++++++++- frontend/src/App.tsx | 48 ++++++++++++++++++- lineageweave/__init__.py | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 9 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 CHANGELOG.d/2.10.0-land-evidence-event-lineage.md diff --git a/CHANGELOG.d/2.10.0-land-evidence-event-lineage.md b/CHANGELOG.d/2.10.0-land-evidence-event-lineage.md new file mode 100644 index 000000000..a5a0f0fba --- /dev/null +++ b/CHANGELOG.d/2.10.0-land-evidence-event-lineage.md @@ -0,0 +1,6 @@ +# 2.10.0 Land Event Lineage under the named evidence next action + +Open Public post from the landed Demo Corp members and Event Lineage +for Linked post sits under the evidence next action, ahead of the +input. Home list opens still keep a single Event Lineage on the +opened post. diff --git a/CHANGELOG.md b/CHANGELOG.md index b6cc075de..2ada0f446 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.10.0] - 2026-08-17 + +### Added + +- Opening Public post from the landed Demo Corp members now puts Event + Lineage for the cited Linked post immediately under the named + evidence next action, ahead of the chat input. Home list opens do + not add that section. No TEPP theta is invented. No cutoff body is + invented (ADR 0016). + ## [2.9.0] - 2026-08-17 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 1bcf50763..067c4e933 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,6 +67,8 @@ 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. 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 +evidence. After landed cited evidence, the popup names Event Lineage +on that post. After that next action, the popup lands Event Lineage +on the cited post. 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 c15e3db5c..58489b343 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.9.0", + "version": "2.10.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index ca589cdfc..58c910872 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -1348,6 +1348,15 @@ describe("App, authenticated", () => { }), ); } + if (url.endsWith("/api/posts/post-2/lineage")) { + return Promise.resolve( + jsonResponse({ + post_id: "post-2", + direct: [{ post_id: "post-1", post_title: "Public post" }], + indirect: [], + }), + ); + } if (url.endsWith("/api/posts/post-1/chat") && method === "GET") { return Promise.resolve( jsonResponse({ @@ -1506,6 +1515,7 @@ describe("App, authenticated", () => { expect(screen.queryByRole("status", { name: "Ask citation next action" })).not.toBeInTheDocument(); expect(screen.queryByRole("status", { name: "Evidence next action" })).not.toBeInTheDocument(); expect(screen.queryByRole("complementary", { name: "Evidence" })).not.toBeInTheDocument(); + expect(screen.queryByText("직접")).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"); @@ -2489,6 +2499,22 @@ describe("App, authenticated", () => { expect(evidenceNext.compareDocumentPosition(askInput) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe( 0, ); + const lineageHeadings = within(popup as HTMLElement).getAllByRole("heading", { + name: "Event Lineage", + }); + expect(lineageHeadings).toHaveLength(2); + const landedLineage = lineageHeadings[1]; + expect( + evidenceNext.compareDocumentPosition(landedLineage) & Node.DOCUMENT_POSITION_FOLLOWING, + ).not.toBe(0); + expect( + landedLineage.compareDocumentPosition(askInput) & Node.DOCUMENT_POSITION_FOLLOWING, + ).not.toBe(0); + expect(landedLineage.compareDocumentPosition(affiliate) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe( + 0, + ); + expect(await within(popup as HTMLElement).findByText("직접")).toBeInTheDocument(); + expect(screen.getByText("직접").closest("li")).toHaveTextContent("Public post"); await waitFor(() => expect(document.getElementById("post-ask")).toHaveFocus()); } finally { HTMLElement.prototype.scrollIntoView = originalScrollIntoView; @@ -2670,7 +2696,10 @@ describe("App, authenticated", () => { fetchMock.releaseMe(); expect( await screen.findByRole("button", { name: "Request a lineage reconstruction" }), - ).toBeEnabled(); + ).toBeInTheDocument(); + await waitFor(() => + expect(screen.getByRole("button", { name: "Request a lineage reconstruction" })).toBeEnabled(), + ); expect( await screen.findByRole("combobox", { name: "Corporate entity to reconstruct" }), ).toBeInTheDocument(); @@ -2864,6 +2893,17 @@ describe("App, authenticated", () => { expect(await screen.findByRole("status", { name: "Evidence next action" })).toHaveTextContent( "Linked post evidence is current. Read Event Lineage on that post next.", ); + const lineageHeadings = within(popup as HTMLElement).getAllByRole("heading", { + name: "Event Lineage", + }); + expect(lineageHeadings).toHaveLength(2); + expect( + screen.getByRole("status", { name: "Evidence next action" }).compareDocumentPosition( + lineageHeadings[1], + ) & Node.DOCUMENT_POSITION_FOLLOWING, + ).not.toBe(0); + expect(await within(popup as HTMLElement).findByText("직접")).toBeInTheDocument(); + expect(screen.getByText("직접").closest("li")).toHaveTextContent("Public post"); await waitFor(() => expect(document.getElementById("post-ask")).toHaveFocus()); }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index afb493fd1..34957ddc0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -165,10 +165,12 @@ function ChatPanel({ postId, accessToken, nameFirstAsk, + onSelectPost, }: { postId: string; accessToken: string; nameFirstAsk?: boolean; + onSelectPost?: (postId: string) => void; }) { const [question, setQuestion] = useState(""); const [exchanges, setExchanges] = useState([]); @@ -176,6 +178,7 @@ function ChatPanel({ const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [evidencePostId, setEvidencePostId] = useState(null); + const [evidenceLineage, setEvidenceLineage] = useState(null); const [seededOnly, setSeededOnly] = useState(false); useEffect(() => { @@ -222,6 +225,31 @@ function ChatPanel({ (firstCitedPostId ? firstCitedPostId.slice(0, 8) : null); const landedEvidencePostId = nameFirstAsk ? (evidencePostId ?? firstCitedPostId) : null; + useEffect(() => { + if (!landedEvidencePostId) { + setEvidenceLineage(null); + return; + } + let cancelled = false; + setEvidenceLineage(null); + fetchPostLineage(accessToken, landedEvidencePostId) + .then((row) => { + if (!cancelled) setEvidenceLineage(row); + }) + .catch(() => { + if (!cancelled) { + setEvidenceLineage({ + post_id: landedEvidencePostId, + direct: [], + indirect: [], + }); + } + }); + return () => { + cancelled = true; + }; + }, [accessToken, landedEvidencePostId]); + return (

@@ -267,6 +295,19 @@ function ChatPanel({ {landedEvidenceNextAction(firstCitedTitle)}

) : null} + {nameFirstAsk && landedEvidencePostId ? ( +
+

+ Event Lineage +

+ +
+ ) : null} {!seededOnly && (
) : null} {afterList && landFirstRelated && landedRelatedName && landedRelated !== null ? ( - + ) : null} ); diff --git a/lineageweave/__init__.py b/lineageweave/__init__.py index ec76789c6..7c789d1b7 100644 --- a/lineageweave/__init__.py +++ b/lineageweave/__init__.py @@ -55,4 +55,4 @@ "sentence_excerpts", ] -__version__ = "2.9.0" +__version__ = "2.10.0" diff --git a/pyproject.toml b/pyproject.toml index 3dcf9a7eb..87211f60b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.9.0" +version = "2.10.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 c12c6964e..dc5fdae3e 100644 --- a/uv.lock +++ b/uv.lock @@ -454,7 +454,7 @@ wheels = [ [[package]] name = "lineageweave" -version = "2.9.0" +version = "2.10.0" source = { virtual = "." } dependencies = [ { name = "certifi" },