diff --git a/CHANGELOG.d/1.1.0-analysis-run-opened-report-members.md b/CHANGELOG.d/1.1.0-analysis-run-opened-report-members.md new file mode 100644 index 00000000..d4a9b2c0 --- /dev/null +++ b/CHANGELOG.d/1.1.0-analysis-run-opened-report-members.md @@ -0,0 +1,5 @@ +# 1.1.0 Land Demo Corp members under the opened-report next action + +Open period report 2026-W02 puts Demo Corp mean θ and member posts +immediately under the next action, ahead of Other Corp. Mean θ stays +on the report panel. diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9a3aa0..0aa82319 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). +## [1.1.0] - 2026-08-17 + +### Added + +- Opening **Open period report 2026-W02** now puts the Demo Corp + report (mean θ and member posts) immediately under the named next + action, ahead of Other Corp and the week strip. The Public post + member stays clickable. Mean θ stays on the report panel. No TEPP + theta is invented. No cutoff body is invented (ADR 0016). + ## [1.0.0] - 2026-08-17 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 4f9b0b18..a9b7bfd7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,6 +46,7 @@ then **Open period report 2026-W02**. The home week is already grouping is Corporate entity and Demo Corp is current. The focused chip name contains `Corporate entity: Demo Corp` and the persisted mean θ. The period-report panel says Demo Corp is the opened grouping -and to read its mean θ and member posts, then open a post. Changing -the week first still focuses the report period field. Mean θ stays on -the period-report panel. +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. Changing the week first still focuses the report +period field. Mean θ stays on the period-report panel. diff --git a/docs/adr/0024-seed-period-report-analysis-run.md b/docs/adr/0024-seed-period-report-analysis-run.md index 9e824e7e..3a8e29c0 100644 --- a/docs/adr/0024-seed-period-report-analysis-run.md +++ b/docs/adr/0024-seed-period-report-analysis-run.md @@ -53,9 +53,10 @@ current, using the persisted scope grouping key. When the operator is already on that week, the comparison strip lands on Demo Corp. The focused chip name contains the visible Corporate entity caption and the persisted mean θ. The period-report panel names the next action: -read that grouping's mean θ and member posts, then open a post. Mean θ -remains on the period-report panel. Re-seed is idempotent on -`demo-report-seed-2026-w02`. +read that grouping's mean θ and member posts, then open a post. Those +members land immediately under that next action, ahead of other +groupings and the week strip. Mean θ remains on the period-report +panel. Re-seed is idempotent on `demo-report-seed-2026-w02`. ## References — APA 7th diff --git a/frontend/package.json b/frontend/package.json index 30a383f9..17ae5ab3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "1.0.0", + "version": "1.1.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index e0335f53..fbb93620 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import App from "./App"; @@ -2191,6 +2191,16 @@ describe("App, authenticated", () => { ); expect(await screen.findByText(/Demo Corp: mean θ 0\.42/)).toBeInTheDocument(); expect(screen.queryByText(/corp-1: mean θ/)).not.toBeInTheDocument(); + const openedReport = screen.getByRole("list", { name: "Opened grouping report" }); + expect(openedReport.textContent ?? "").toMatch(/Demo Corp: mean θ 0\.42[\s\S]*Other Corp: mean θ/); + expect( + within(openedReport).getByRole("button", { name: /open report post: public post/i }), + ).toBeInTheDocument(); + const status = screen.getByRole("status"); + const demoMean = screen.getByText(/Demo Corp: mean θ 0\.42/); + const weekChip = screen.getByRole("button", { name: /open report period 2026-W03/i }); + expect(status.compareDocumentPosition(demoMean) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0); + expect(demoMean.compareDocumentPosition(weekChip) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0); }); it("lands the comparison strip on Demo Corp when already on that week", async () => { @@ -2227,6 +2237,24 @@ describe("App, authenticated", () => { "Demo Corp is the opened grouping. Read its mean θ and member posts below, then open a post.", ); expect(await screen.findByText(/Demo Corp: mean θ 0\.42/)).toBeInTheDocument(); + const openedReport = screen.getByRole("list", { name: "Opened grouping report" }); + expect(within(openedReport).getByText(/Demo Corp: mean θ 0\.42/).closest("li")).toHaveAttribute( + "aria-current", + "true", + ); + expect(openedReport.textContent ?? "").toMatch(/Demo Corp: mean θ 0\.42[\s\S]*Other Corp: mean θ/); + expect(openedReport.textContent ?? "").not.toMatch(/Other Corp: mean θ[\s\S]*Demo Corp: mean θ 0\.42/); + const member = within(openedReport).getByRole("button", { + name: /open report post: public post/i, + }); + expect(member).toHaveTextContent("θ 0.91"); + const status = screen.getByRole("status"); + const demoMean = screen.getByText(/Demo Corp: mean θ 0\.42/); + const weekChip = screen.getByRole("button", { name: /open report period 2026-W03/i }); + expect(status.compareDocumentPosition(demoMean) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0); + expect(demoMean.compareDocumentPosition(weekChip) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0); + await userEvent.click(member); + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); } finally { HTMLElement.prototype.scrollIntoView = originalScrollIntoView; } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 921959e0..63eb0fc3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2116,6 +2116,18 @@ function openedReportNextAction(groupingLabel: string): string { ); } +function openedReportsFirst( + reports: T[], + isOpened: (groupingKey: string, groupingLabel?: string) => boolean, +): T[] { + const opened = reports.filter((report) => isOpened(report.grouping_key, report.grouping_label)); + if (opened.length === 0) { + return reports; + } + const rest = reports.filter((report) => !isOpened(report.grouping_key, report.grouping_label)); + return [...opened, ...rest]; +} + function ReportsPanel({ accessToken, canRebuild, @@ -2205,6 +2217,85 @@ function ReportsPanel({ } } + const orderedReports = payload + ? openedReportsFirst(payload.reports, (groupingKey, groupingLabel) => + groupingIsOpened(grouping, groupingKey, groupingLabel), + ) + : []; + const reportList = + payload === null && !error ? ( +

Loading reports...

+ ) : payload && payload.reports.length === 0 ? ( +

+ No calibrated report for this grouping and period. Evaluate posts, then rebuild. +

+ ) : payload && payload.reports.length > 0 ? ( + + ) : null; + return (
@@ -2275,6 +2366,7 @@ function ReportsPanel({ {openedReportNextAction(openedGroupingLabel)}

)} + {openedGroupingLabel && reportList} {index && index.periods.length > 0 && (
    {index.periods.map((row) => ( @@ -2306,75 +2398,7 @@ function ReportsPanel({
)} {error &&

{error}

} - {payload === null && !error &&

Loading reports...

} - {payload && payload.reports.length === 0 && ( -

- No calibrated report for this grouping and period. Evaluate posts, then rebuild. -

- )} - {payload && payload.reports.length > 0 && ( -
    - {payload.reports.map((report) => ( -
  • - - {report.grouping_label ?? report.grouping_key}: mean θ {report.mean_theta.toFixed(2)} ({report.selected_model} - {report.fit_converged ? ", converged" : ", not converged"}) - - {report.post_count} posts - {report.link_method === "fipc" && report.anchor_period_code && report.delta_mean_theta != null && ( - - vs {report.anchor_period_code}: {report.delta_mean_theta >= 0 ? "+" : ""} - {report.delta_mean_theta.toFixed(2)} - - )} - {report.link_method === "fipc" && report.delta_mean_theta == null && ( - shared metric - )} - {report.selected_items?.[0] && ( - - CAT: {criterionShortLabel(report.selected_items[0].item_code)} I= - {report.selected_items[0].information.toFixed(2)} - - )} - {report.members.length > 0 && ( -
      - {report.members.map((member) => ( -
    • - -
    • - ))} -
    - )} -
  • - ))} -
- )} + {!openedGroupingLabel && reportList}
); } diff --git a/lineageweave/__init__.py b/lineageweave/__init__.py index 107bfdb6..fc6a0d64 100644 --- a/lineageweave/__init__.py +++ b/lineageweave/__init__.py @@ -55,4 +55,4 @@ "sentence_excerpts", ] -__version__ = "1.0.0" +__version__ = "1.1.0" diff --git a/pyproject.toml b/pyproject.toml index 6510d6e3..9b7dfcb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "1.0.0" +version = "1.1.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 7ea52e06..e407904c 100644 --- a/uv.lock +++ b/uv.lock @@ -454,7 +454,7 @@ wheels = [ [[package]] name = "lineageweave" -version = "1.0.0" +version = "1.1.0" source = { virtual = "." } dependencies = [ { name = "certifi" },