diff --git a/CHANGELOG.d/1.5.0-event-lineage-keyman-eval-next.md b/CHANGELOG.d/1.5.0-event-lineage-keyman-eval-next.md new file mode 100644 index 000000000..f047f5137 --- /dev/null +++ b/CHANGELOG.d/1.5.0-event-lineage-keyman-eval-next.md @@ -0,0 +1,4 @@ +# 1.5.0 Name Keyman and evaluation after the current Event Lineage node + +Open Public post from the landed Demo Corp members and the popup names +Keyman and evaluation after the current Event Lineage node. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a0f85648..8860a5b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ 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). +## [1.5.0] - 2026-08-17 + +### Added + +- Opening Public post from the landed Demo Corp members names the next + action after the current Event Lineage node: read Keyman and + evaluation. Home list opens do not add that copy. No TEPP theta is + invented. No cutoff body is invented (ADR 0016). + ## [1.4.0] - 2026-08-17 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index f73c15501..92725c1ae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,6 +49,8 @@ mean θ. The period-report panel says Demo Corp is the opened grouping and to read its mean θ and member posts, then open a post. Those members land immediately under that next action, ahead of Other Corp and the week strip. Opening Public post names the next action: read -Event Lineage, Keyman, and evaluation on that post. Changing the week +Event Lineage, Keyman, and evaluation on that post. The popup Event +Lineage DAG marks that post current. After that current node, the +popup names Keyman and evaluation as the next read. 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 c29108ea1..8bc2c48e2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "1.4.0", + "version": "1.5.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index f96a80009..3aa56e4d3 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -1471,6 +1471,9 @@ describe("App, authenticated", () => { expect(screen.getAllByLabelText("A-100 lineage").length).toBeGreaterThanOrEqual(2); expect(screen.getAllByLabelText("Open post: Pricing renegotiation follow-up").length).toBeGreaterThanOrEqual(2); expect(document.getElementById("post-event-lineage")).not.toHaveFocus(); + expect( + screen.queryByRole("status", { name: "Event Lineage next action" }), + ).not.toBeInTheDocument(); }); it("shows a seeded Ask exchange without an orchestrator round-trip", async () => { @@ -2258,24 +2261,40 @@ describe("App, authenticated", () => { await userEvent.click(member); await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); expect(member).toHaveAttribute("aria-current", "true"); - expect(screen.getByRole("status")).toHaveTextContent( - "Public post is open from Demo Corp. Read Event Lineage, Keyman, and evaluation on this post.", - ); - expect(screen.getByRole("status")).not.toHaveTextContent("then open a post"); + expect( + screen.getByText( + "Public post is open from Demo Corp. Read Event Lineage, Keyman, and evaluation on this post.", + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + "Public post is open from Demo Corp. Read Event Lineage, Keyman, and evaluation on this post.", + ), + ).not.toHaveTextContent("then open a post"); expect( screen.getAllByRole("heading", { name: "Event Lineage" }).length, ).toBeGreaterThanOrEqual(2); expect(document.getElementById("post-event-lineage")).toHaveFocus(); const popup = document.querySelector(".popup-panel"); expect(popup).not.toBeNull(); - expect(within(popup as HTMLElement).getByLabelText("Open post: Public post")).toHaveAttribute( - "aria-current", - "true", - ); + const currentNode = within(popup as HTMLElement).getByLabelText("Open post: Public post"); + expect(currentNode).toHaveAttribute("aria-current", "true"); const homeNode = screen .getAllByLabelText("Open post: Public post") .find((node) => !popup?.contains(node)); expect(homeNode).not.toHaveAttribute("aria-current"); + const lineageNext = screen.getByRole("status", { name: "Event Lineage next action" }); + expect(lineageNext).toHaveTextContent( + "Public post is current in Event Lineage. Read Keyman and evaluation next.", + ); + expect( + currentNode.compareDocumentPosition(lineageNext) & Node.DOCUMENT_POSITION_FOLLOWING, + ).not.toBe(0); + expect( + lineageNext.compareDocumentPosition( + within(popup as HTMLElement).getByRole("heading", { name: "Keyman" }), + ) & Node.DOCUMENT_POSITION_FOLLOWING, + ).not.toBe(0); } finally { HTMLElement.prototype.scrollIntoView = originalScrollIntoView; } @@ -2540,6 +2559,9 @@ describe("App, authenticated", () => { expect(screen.getAllByText(/Ada West/).length).toBeGreaterThan(0); expect(screen.getAllByLabelText("A-100 lineage").length).toBeGreaterThanOrEqual(2); expect(document.getElementById("post-event-lineage")).toHaveFocus(); + expect(screen.getByRole("status", { name: "Event Lineage next action" })).toHaveTextContent( + "Public post is current in Event Lineage. Read Keyman and evaluation next.", + ); }); it("lets post_admin rebuild the period report", async () => { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 81f2173b8..d0de5ce18 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -271,16 +271,22 @@ function ChatPanel({ postId, accessToken }: { postId: string; accessToken: strin ); } +function eventLineageCurrentNextAction(postTitle: string): string { + return `${postTitle} is current in Event Lineage. Read Keyman and evaluation next.`; +} + function EventLineageSection({ lineage, graph, postId, onSelectPost, + currentNextAction, }: { lineage: PostLineage | null; graph: LineageGraph | null; postId: string; onSelectPost?: (postId: string) => void; + currentNextAction?: string | null; }) { if (!lineage) return

Loading lineage...

; const scoped = graph ? subgraphForPost(graph, postId) : { nodes: [], edges: [] }; @@ -305,6 +311,11 @@ function EventLineageSection({ {scoped.nodes.length > 0 && onSelectPost && ( )} + {scoped.nodes.length > 0 && currentNextAction ? ( +

+ {currentNextAction} +

+ ) : null} {hasLinks && (