-
Notifications
You must be signed in to change notification settings - Fork 1
feat: warn that cutoff-rewritten titles open the live body (v0.88.0) #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # 0.88.0 Analysis-run cutoff body warning | ||
|
|
||
| Opening a title marked updated after cutoff now says the popup body is | ||
| live. The earlier text is not stored, so the popup does not invent it. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1754,6 +1754,42 @@ describe("App, authenticated", () => { | |
| expect(teppHistory).not.toHaveTextContent("Succeeded"); | ||
| }); | ||
|
|
||
| it("warns that a cutoff-rewritten title opens the live body, not a snapshot", async () => { | ||
| stubBackend(); | ||
| render(<App />); | ||
|
|
||
| await userEvent.click( | ||
| await screen.findByRole("button", { | ||
| name: "Open analysis run: Lineage reconstruction · Succeeded · Demo Corp", | ||
| }), | ||
| ); | ||
| await userEvent.click( | ||
| await screen.findByRole("button", { | ||
| name: "Open live post (updated after cutoff): Public post", | ||
| }), | ||
| ); | ||
| await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); | ||
| expect(screen.getByRole("status")).toHaveTextContent( | ||
| "This is the live body, not the version known at the 2026-01-12 analysis-run cutoff. The earlier text is not stored, so this popup does not invent it.", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This locks the honesty sentence. After the copy change, assert the compare-with-this-run action. Also walk |
||
| ); | ||
|
|
||
| await userEvent.click(screen.getByRole("button", { name: "Close" })); | ||
| await userEvent.click( | ||
| screen.getByRole("button", { | ||
| name: "Open live post: Private post", | ||
| }), | ||
| ); | ||
| await waitFor(() => | ||
| expect(screen.getByText("The evidence panel should show exactly this text.")).toBeInTheDocument(), | ||
| ); | ||
| expect(screen.queryByRole("status")).not.toBeInTheDocument(); | ||
|
|
||
| await userEvent.click(screen.getByRole("button", { name: "Close" })); | ||
| await userEvent.click(screen.getByRole("button", { name: "View post: Public post" })); | ||
| await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); | ||
| expect(screen.queryByRole("status")).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("does not tell a failed lineage run to connect the measurement service", async () => { | ||
| stubBackend({ failedLineageRun: true }); | ||
| render(<App />); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1163,13 +1163,15 @@ function PostDetailPopup({ | |
| accessToken, | ||
| canExtract, | ||
| graph, | ||
| liveBodyWarning, | ||
| onClose, | ||
| onSelectPost, | ||
| }: { | ||
| postId: string; | ||
| accessToken: string; | ||
| canExtract: boolean; | ||
| graph: LineageGraph | null; | ||
| liveBodyWarning?: string | null; | ||
| onClose: () => void; | ||
| onSelectPost?: (postId: string) => void; | ||
| }) { | ||
|
|
@@ -1246,6 +1248,11 @@ function PostDetailPopup({ | |
| {post.visibility_label ?? post.visibility_code} ·{" "} | ||
| {new Date(post.created_at).toLocaleString()} | ||
| </p> | ||
| {liveBodyWarning ? ( | ||
| <p className="popup-live-body-warning" role="status"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the only |
||
| {liveBodyWarning} | ||
| </p> | ||
| ) : null} | ||
| <PostBody body={post.post_body} /> | ||
|
|
||
| <section className="popup-section"> | ||
|
|
@@ -1545,12 +1552,18 @@ function analysisRunDigestPrefix(digest: string): string { | |
| return digest.slice(0, ANALYSIS_RUN_DIGEST_PREFIX_LENGTH); | ||
| } | ||
|
|
||
| type SelectPostOptions = { | ||
| liveAfterCutoff?: boolean; | ||
| knowledgeCutoff?: string; | ||
| }; | ||
|
|
||
| /** | ||
| * Next action when a cutoff title opens the live post (ADR 0016). | ||
| * | ||
| * Post-body versioning is a later slice. Titles marked | ||
| * `live_after_cutoff` were rewritten after this run; others still | ||
| * match the write clock the run knew. | ||
| * Titles marked `live_after_cutoff` were rewritten after this run; | ||
| * others still match the write clock the run knew. The popup then | ||
| * states that the body is live. Cutoff body versioning stays later | ||
| * work -- we never invent the earlier text. | ||
| */ | ||
| function analysisRunLivePostWarning(cutoffIso: string): string { | ||
| const cutoffDate = cutoffIso.slice(0, 10); | ||
|
|
@@ -1561,6 +1574,21 @@ function analysisRunLivePostWarning(cutoffIso: string): string { | |
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Popup honesty when a marked cutoff title opens the live body. | ||
| * | ||
| * ADR 0016 does not store a historical snapshot. This copy must not | ||
| * invent the earlier text. | ||
| */ | ||
| function analysisRunOpenedBodyWarning(cutoffIso?: string | null): string { | ||
| const cutoffDate = cutoffIso?.slice(0, 10); | ||
| const when = cutoffDate ? `the ${cutoffDate} ` : ""; | ||
| return ( | ||
| `This is the live body, not the version known at ${when}analysis-run cutoff. ` + | ||
| "The earlier text is not stored, so this popup does not invent it." | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sentence is developer honesty, not the next action. The run-list helper already tells the operator to compare marked bodies with this run. The popup should continue that action, for example: “This is the live body, not a cutoff snapshot. Compare it with this 2026-01-12 run before you treat it as reconstructed evidence.” Do not invent the earlier text. |
||
| ); | ||
| } | ||
|
|
||
| function analysisRunLivePostButtonLabel(post: { | ||
| post_title: string; | ||
| live_after_cutoff?: boolean; | ||
|
|
@@ -1606,7 +1634,7 @@ function AnalysisRunsPanel({ | |
| onSelectPost, | ||
| }: { | ||
| accessToken: string; | ||
| onSelectPost: (postId: string) => void; | ||
| onSelectPost: (postId: string, options?: SelectPostOptions) => void; | ||
| }) { | ||
| const [runs, setRuns] = useState<AnalysisRun[] | null>(null); | ||
| const [selected, setSelected] = useState<AnalysisRun | null>(null); | ||
|
|
@@ -1742,7 +1770,12 @@ function AnalysisRunsPanel({ | |
| <button | ||
| className="keyman-select" | ||
| aria-label={analysisRunLivePostButtonLabel(post)} | ||
| onClick={() => onSelectPost(post.post_id)} | ||
| onClick={() => | ||
| onSelectPost(post.post_id, { | ||
| liveAfterCutoff: Boolean(post.live_after_cutoff), | ||
| knowledgeCutoff: selected.knowledge_cutoff, | ||
| }) | ||
| } | ||
| > | ||
| {post.post_title} | ||
| </button> | ||
|
|
@@ -2018,10 +2051,24 @@ function PostList({ accessToken }: { accessToken: string }) { | |
| const [graph, setGraph] = useState<LineageGraph | null>(null); | ||
| const [error, setError] = useState<string | null>(null); | ||
| const [selectedPostId, setSelectedPostId] = useState<string | null>(null); | ||
| const [openedAfterCutoff, setOpenedAfterCutoff] = useState(false); | ||
| const [openedCutoffIso, setOpenedCutoffIso] = useState<string | null>(null); | ||
| const [canRebuild, setCanRebuild] = useState(false); | ||
| const [rebuilding, setRebuilding] = useState(false); | ||
| const [rebuildError, setRebuildError] = useState<string | null>(null); | ||
|
|
||
| function selectPost(postId: string, options?: SelectPostOptions) { | ||
| setSelectedPostId(postId); | ||
| setOpenedAfterCutoff(Boolean(options?.liveAfterCutoff)); | ||
| setOpenedCutoffIso(options?.knowledgeCutoff ?? null); | ||
| } | ||
|
|
||
| function closeSelectedPost() { | ||
| setSelectedPostId(null); | ||
| setOpenedAfterCutoff(false); | ||
| setOpenedCutoffIso(null); | ||
| } | ||
|
|
||
| useEffect(() => { | ||
| fetchPosts(accessToken).then(setPosts).catch((err) => setError(String(err))); | ||
| fetchLineageGraph(accessToken).then(setGraph).catch(() => setGraph({ nodes: [], edges: [] })); | ||
|
|
@@ -2049,9 +2096,9 @@ function PostList({ accessToken }: { accessToken: string }) { | |
|
|
||
| return ( | ||
| <> | ||
| <CalendarPanel accessToken={accessToken} onSelectPost={setSelectedPostId} /> | ||
| <AnalysisRunsPanel accessToken={accessToken} onSelectPost={setSelectedPostId} /> | ||
| <ReportsPanel accessToken={accessToken} canRebuild={canRebuild} onSelectPost={setSelectedPostId} /> | ||
| <CalendarPanel accessToken={accessToken} onSelectPost={selectPost} /> | ||
| <AnalysisRunsPanel accessToken={accessToken} onSelectPost={selectPost} /> | ||
| <ReportsPanel accessToken={accessToken} canRebuild={canRebuild} onSelectPost={selectPost} /> | ||
| <section className="popup-section lineage-home"> | ||
| <div className="lineage-home-header"> | ||
| <h2>Event Lineage</h2> | ||
|
|
@@ -2063,15 +2110,15 @@ function PostList({ accessToken }: { accessToken: string }) { | |
| </div> | ||
| {rebuildError && <p className="error">{rebuildError}</p>} | ||
| {!graph && <p>Loading lineage graph...</p>} | ||
| {graph && <LineageDag graph={graph} onSelectPost={setSelectedPostId} />} | ||
| {graph && <LineageDag graph={graph} onSelectPost={selectPost} />} | ||
| </section> | ||
| <ul className="post-list"> | ||
| {posts.map((post) => ( | ||
| <li key={post.post_id}> | ||
| <button | ||
| className="post-list-item" | ||
| aria-label={`View post: ${post.post_title}`} | ||
| onClick={() => setSelectedPostId(post.post_id)} | ||
| onClick={() => selectPost(post.post_id)} | ||
| > | ||
| <span className="post-title">{post.post_title}</span> | ||
| <span className="post-badge">{post.voc_type_label ?? post.voc_type_code}</span> | ||
|
|
@@ -2086,8 +2133,11 @@ function PostList({ accessToken }: { accessToken: string }) { | |
| accessToken={accessToken} | ||
| canExtract={canRebuild} | ||
| graph={graph} | ||
| onClose={() => setSelectedPostId(null)} | ||
| onSelectPost={setSelectedPostId} | ||
| liveBodyWarning={ | ||
| openedAfterCutoff ? analysisRunOpenedBodyWarning(openedCutoffIso) : null | ||
| } | ||
| onClose={closeSelectedPost} | ||
| onSelectPost={selectPost} | ||
| /> | ||
| )} | ||
| </> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,4 +55,4 @@ | |
| "sentence_excerpts", | ||
| ] | ||
|
|
||
| __version__ = "0.87.0" | ||
| __version__ = "0.88.0" | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New amber hex.
--lw-font-size-metaalready exists for 0.85rem. Add--lw-color-warningbeside the existing--lw-*tokens and use it here. Do not install Storybook on this slice.