Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.d/2.21.2-cancelled-analysis-next-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 2.21.2 Name the next action for every cancelled analysis kind

Cancelled lineage, TEPP, topic-lineage, and period-report runs now tell the
operator the kind-specific valid next action. A terminal cancellation no
longer leaves the analysis list and detail without guidance.
9 changes: 5 additions & 4 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ context only.

| PR | Exact observed head | Merge/check state at this snapshot |
| ---: | --- | --- |
| #667 | `dbc438e2` | baseline refresh branch exact head at this snapshot; BLOCKED with review required |
| #668 | `facbae58` | evidence-bound project history projection with normalized action keys; BLOCKED with review required |
| #667 | `a9731d52` | baseline refresh branch exact head at this snapshot; BLOCKED with review required |
| #668 | `f272f4b0` | evidence-bound project history projection with request guards and normalized keys; BLOCKED with review required |
| #663 | `7ac1483e` | project ontology traversal, cutoff-snapshot project focus, bounded MCP admission, and migration-fixture/worker startup repair; BLOCKED with review required |
| #658 | `f497a6e8` | evidence-honest Global Ask cutoff with robust revision timestamps; BLOCKED with review required |
| #657 | `a59a2023` | TEPP lifecycle persistence; BLOCKED with review required |
Expand Down Expand Up @@ -372,6 +372,7 @@ this file per §3.5 of the prior snapshot).
| --- | --- | --- |
| Protected release | Protected `main@494b54e2` includes the #660/#664 semantic-unit/backend stack and #659 ontology readability/token repair. Twelve PRs remain open; every candidate still requires exact-head checks, unresolved-thread review, and independent approval before merge | Terminal exact-head checks, no unresolved threads, two independent exact-head approvals including last-push approval, protected squash-merge SHA |
| Evidence-grounded operations workspace | Protected-main #614 delivers governed semantic Ask, live Similar VOC, disjoint pending/failed analysis metrics, full Storybook state inventory, and current desktop/mobile screenshot evidence. Authorized-corpus backfill acceptance remains unavailable | Perform authenticated authorized-corpus acceptance with aggregate evidence and retain fail-closed no-match behavior |
| Cancelled analysis guidance | PRD-FR-5 requires every lifecycle state to identify a valid next action, while ADR 0013 makes Cancelled terminal. Protected `main@494b54e2` rendered Cancelled without a next action. This stacked candidate adds kind-specific guidance for lineage, TEPP, topic lineage, and period reports; 390×844 and 1440×1000 authenticated synthetic-runtime audits are retained in `docs/screenshots/cancelled-analysis-runs-{mobile,desktop}.png`. The audit also found and repaired attached count/action text and the three-column mobile squeeze | Land through the protected gate, then repeat authenticated keyboard and screen-reader acceptance on the exact release head; no cancelled run may imply that it can resume or that a measurement exists |
| Shared frontend gate | The ADR 0109 login repair is on protected `main`; eight older branches carried the defect and received the same verified repair this loop (#521–#560) | Keep every future branch cut from post-repair bases; re-verify with frontend lint/test/build before push |
| Identifying baseline regression | `main` gap file listed real post identifiers; separately, closed #506 and pre-existing public history contain a private runtime source-table identifier, while current `main` and #507 trees are clean | Land this non-identifying rewrite, then coordinate ADR 0001 history remediation with security/privacy owners; do not reproduce the value, force-push, or delete evidence ad hoc |
| Authorized-corpus runtime | Repository tests use synthetic fixtures; private records remain outside git | Authenticated runtime validation returning only aggregate, non-identifying evidence |
Expand Down Expand Up @@ -408,10 +409,10 @@ before a commercial release claim. Figma File ID `1Su3lDRmiZdcUs47t1QwIX`.
| Touch & Interaction | Click-first popup and lists | 44px targets, swipe/escape to dismiss popup, no hover-only actions |
| Performance | Board caps and hint render limits exist | Interaction-to-next-paint on board search, DAG, Ask; no N+1 query regressions |
| Style Selection | Korean UI standards merged (#347) | Tokenized light/dark; Anti-Slop-UI density; no decorative noise |
| Layout & Responsive | Desktop popup shell | 402px-class phone layout; stacked GNB; readable DAG |
| Layout & Responsive | Desktop popup shell; this candidate stacks analysis-run caption, document count, and next action at 390px and retains explicit grid columns at 1440px | Complete 402px-class phone acceptance for remaining panels; stacked GNB; readable DAG |
| Typography & Color | Badge tokens extracted | Contrast on badges, links, error/status; no raw hex in components |
| Animation | Minimal | Reduced-motion; no blocking animation on evidence open |
| Forms & Feedback | Login, Ask, tickets, admin brand | Inline validation, next-action copy, unavailable vs failed distinction |
| Forms & Feedback | Login, Ask, tickets, admin brand; this candidate gives all four Cancelled analysis kinds an evidence-safe next action | Inline validation and unavailable-vs-failed acceptance across remaining workflows |
| Navigation Patterns | Board / customers / calendar / Ask / admin | Deep-link post + OIDC return URL (#426); bookmarkable Ask |
| Charts & Data | Period reports, leftover pairs, Rankings, DAG | Honest empty/unavailable; no invented theta; Storybook chart states |

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,27 @@
gap: 0.5rem;
}

.analysis-run-item {
display: grid;
gap: 0.75rem;
}
Comment thread
seonghobae marked this conversation as resolved.

.analysis-run-item.has-document-count.has-next-action {
grid-template-columns: minmax(12rem, 1fr) auto minmax(18rem, 2fr);
}

.analysis-run-item.has-document-count:not(.has-next-action) {
grid-template-columns: minmax(12rem, 1fr) auto;
}
Comment thread
seonghobae marked this conversation as resolved.

.analysis-run-item.has-next-action:not(.has-document-count) {
grid-template-columns: minmax(12rem, 1fr) minmax(18rem, 2fr);
}

.analysis-run-item:not(.has-document-count):not(.has-next-action) {
grid-template-columns: 1fr;
}

.ranking-hit {
flex-direction: column;
align-items: stretch;
Expand Down Expand Up @@ -1115,6 +1136,14 @@
align-items: flex-start;
gap: 0.5rem;
}

.analysis-run-item,
.analysis-run-item.has-document-count.has-next-action,
.analysis-run-item.has-document-count:not(.has-next-action),
.analysis-run-item.has-next-action:not(.has-document-count),
.analysis-run-item:not(.has-document-count):not(.has-next-action) {
grid-template-columns: 1fr;
}
}

.ontology-explorer {
Expand Down
54 changes: 54 additions & 0 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { fireEvent, render, screen, waitFor, within } from "@testing-library/rea
import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import App from "./App";
import type { AnalysisRunKindCode } from "./api";
import { setLocale } from "./i18n";
import { OIDC_RETURN_URL_STORAGE_KEY } from "./oidcReturnUrl";

Expand Down Expand Up @@ -106,6 +107,7 @@ describe("App, authenticated", () => {
succeededReportRun?: boolean;
succeededTeppRun?: boolean;
pendingTeppRun?: boolean;
cancelledRunKind?: AnalysisRunKindCode;
pluralAffiliations?: boolean;
deferMe?: boolean;
deferPostOne?: boolean;
Expand Down Expand Up @@ -161,6 +163,27 @@ describe("App, authenticated", () => {
releasePostOne = resolve;
})
: Promise.resolve();
const cancelledRun = options?.cancelledRunKind
? {
analysis_run_id: "run-demo-cancelled",
run_kind_code: options.cancelledRunKind,
run_kind_label: {
analysis_run_lineage: "Lineage reconstruction",
analysis_run_tepp: "TEPP measurement",
analysis_run_topic_lineage: "Topic lineage",
analysis_run_report: "Period report",
}[options.cancelledRunKind],
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
status_code: "analysis_status_cancelled" as const,
status_label: "Cancelled",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:42:00Z",
source_counts: [],
visible_posts: [],
}
: null;

const fetchMock = vi.fn((input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
Expand Down Expand Up @@ -269,6 +292,9 @@ describe("App, authenticated", () => {
jsonResponse({ post_id: "post-1", has_commitment: true, ticket }),
);
}
if (url.endsWith("/api/analysis-runs/run-demo-cancelled") && cancelledRun) {
return Promise.resolve(jsonResponse(cancelledRun));
}
if (url.endsWith("/api/analysis-runs/run-demo-report")) {
const reportSucceeded = !options?.failedReportRun;
return Promise.resolve(
Expand Down Expand Up @@ -686,6 +712,7 @@ describe("App, authenticated", () => {
return Promise.resolve(
jsonResponse({
analysis_runs: [
...(cancelledRun ? [cancelledRun] : []),
...(createdPendingLineage ? [createdPendingLineage] : []),
...(createdPendingTepp ? [createdPendingTepp] : []),
{
Expand Down Expand Up @@ -3399,6 +3426,33 @@ describe("App, authenticated", () => {
expect(screen.queryByRole("heading", { name: "Body this run knew" })).not.toBeInTheDocument();
});

it.each([
["analysis_run_lineage", "Request a new lineage reconstruction from a current snapshot."],
[
"analysis_run_tepp",
"Connect the measurement service, then ask an administrator to submit a new TEPP run from a current snapshot.",
],
[
"analysis_run_topic_lineage",
"Connect the TEPP transport, then ask an administrator to submit new topic-lineage analysis from a current snapshot.",
],
["analysis_run_report", "Rebuild the period report from a current snapshot."],
] satisfies [AnalysisRunKindCode, string][])(
"gives a cancelled %s run its kind-exact next action",
async (runKindCode, nextAction) => {
stubBackend({ cancelledRunKind: runKindCode });
render(<App showLabPanels />);

const cancelledRunButton = await screen.findByRole("button", {
name: /Open analysis run: .* · Cancelled · Demo Corp/,
});
expect(cancelledRunButton).toHaveTextContent(`This run was cancelled. ${nextAction}`);
await userEvent.click(cancelledRunButton);
expect(screen.getAllByText(`This run was cancelled. ${nextAction}`)).not.toHaveLength(0);
expect(screen.queryByRole("button", { name: /Start/ })).not.toBeInTheDocument();
},
);

it("tells a running lineage run to refresh the durable outbox", async () => {
stubBackend({ runningLineageRun: true });
render(<App showLabPanels />);
Expand Down
18 changes: 16 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2701,8 +2701,22 @@ function analysisRunNextAction(run: AnalysisRun): string | null {
}
case "analysis_status_running":
return "Refresh this run. Start already queued the work on the durable outbox.";
case "analysis_status_succeeded":
case "analysis_status_cancelled":
switch (run.run_kind_code) {
case "analysis_run_lineage":
return "This run was cancelled. Request a new lineage reconstruction from a current snapshot.";
case "analysis_run_tepp":
return "This run was cancelled. Connect the measurement service, then ask an administrator to submit a new TEPP run from a current snapshot.";
case "analysis_run_topic_lineage":
return "This run was cancelled. Connect the TEPP transport, then ask an administrator to submit new topic-lineage analysis from a current snapshot.";
case "analysis_run_report":
return "This run was cancelled. Rebuild the period report from a current snapshot.";
default: {
const unexpected: never = run.run_kind_code;
return unexpected;
}
}
case "analysis_status_succeeded":
Comment thread
seonghobae marked this conversation as resolved.
case null:
return null;
default: {
Expand Down Expand Up @@ -3152,7 +3166,7 @@ function AnalysisRunsPanel({
return (
<li key={run.analysis_run_id} className="ticket-list-item">
<button
className="post-list-item"
className={`post-list-item analysis-run-item${documentCount ? " has-document-count" : ""}${nextAction ? " has-next-action" : ""}`}
aria-label={`Open analysis run: ${caption}`}
onClick={() => void handleOpen(run.analysis_run_id)}
>
Expand Down
Loading