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 00000000..a5a0f0fb --- /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 b6cc075d..2ada0f44 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 1bcf5076..067c4e93 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 c15e3db5..58489b34 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 ca589cdf..58c91087 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 afb493fd..34957ddc 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 ec76789c..7c789d1b 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 3dcf9a7e..87211f60 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 c12c6964..dc5fdae3 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" },