Skip to content

feat(console): complete goal stack reconciliation - #95

Merged
Travis-Gilbert merged 1 commit into
mainfrom
Travis-Gilbert/goal-stack-reconciliation
Jul 23, 2026
Merged

feat(console): complete goal stack reconciliation#95
Travis-Gilbert merged 1 commit into
mainfrom
Travis-Gilbert/goal-stack-reconciliation

Conversation

@Travis-Gilbert

@Travis-Gilbert Travis-Gilbert commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Goal Stack now supports resizable panels that adapt between desktop and narrow-screen layouts.
    • Tasks display clearer approval, escalation, and changed-status indicators.
    • Task details include assigned-head information and improved action guidance.
    • Promotion dialogs identify when external effects remain advisory.
  • Bug Fixes
    • Improved deep-link and back/forward navigation across console surfaces.
    • Improved plan refresh behavior and handling of refused actions.
    • Sidebar collapse and surface switching now behave more consistently.
  • Style
    • Goal progress animations now respect shared motion settings and reduced-motion preferences.

Copilot AI review requested due to automatic review settings July 22, 2026 21:25
@ecc-tools

ecc-tools Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR extends Goal Stack plan normalization with escalation and degraded-stream handling, materializes plans as advisory programs, updates responsive Goal Stack and sidebar behavior, and expands unit, route, and E2E coverage.

Changes

Goal Stack execution and presentation

Layer / File(s) Summary
Plan state normalization and escalation
packages/theorem-acp/src/plan-state.ts, apps/console/src/lib/goal-stack-contract.test.ts
Plan snapshots and events now preserve history, degraded cursors, changed events, cancellation/input-required states, and escalation details.
Advisory program materialization and plan routes
packages/theorem-acp/src/plan-program.ts, apps/console/src/app/api/harness/plan/route.ts, apps/console/src/lib/plan-route.test.ts
Saved programs use advisory metadata and filtered bindings; poll cursors, runs-rail refreshes, and nested refusal responses receive updated handling.
Goal Stack layout and task presentation
apps/console/src/views/goal-stack/*, apps/console/src/motion/motion.css
The view uses responsive resizable panes, task actions and badges reflect escalation and state, and edge motion uses shared timing variables.
Shell synchronization and E2E validation
apps/console/src/components/shell/*, apps/console/e2e/workspace-goal-stack.spec.ts
Surface routing, compact sidebar rendering, readiness checks, plan opening, drag interactions, and responsive screenshots are updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PlanEvents
  participant normalizePlanPoll
  participant GoalStackView
  participant PlanTaskNode
  PlanEvents->>normalizePlanPoll: task transitions and escalation details
  normalizePlanPoll->>GoalStackView: normalized snapshot
  GoalStackView->>PlanTaskNode: task status and escalation fields
  PlanTaskNode-->>GoalStackView: escalation and changed badges
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main theme of the changes: console goal stack reconciliation across routing, state normalization, and UI updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Travis-Gilbert/goal-stack-reconciliation

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59221757a4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +242 to +244
const foldEvents = inspected
? events.filter((event) => event.graphVersion > inspected.streamCursor)
: events;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve recovered events after a degraded poll

When what_changed fails, the route deliberately retains the old cursor, but the fresh inspect snapshot can still carry a newer watermark. On the first successful poll after recovery, this filter drops every replayed event at or below that inspect watermark, including the previously unavailable interval. The snapshot preserves current task status, but event-only projection data such as changed paths and escalation receipts is permanently lost; filter against the last successfully consumed event cursor when recovery is in progress instead.

Useful? React with 👍 / 👎.

Comment on lines +477 to +480
progressFraction: task.progressFraction ?? prior.progressFraction,
progressNote: task.progressNote ?? prior.progressNote,
assignedHead: task.assignedHead ?? prior.assignedHead,
escalation: task.escalation ?? prior.escalation,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not retain optional task state after inspect clears it

When a later authoritative inspect response clears an optional field, this merge restores the previous value instead. For example, a task that is reassigned or resumes after escalation can return no assigned_head or escalation, yet the canvas continues to show its former target and escalation receipt; similarly, a reset task continues to show its old progress. Preserve historical event data separately, but let explicit fresh task state replace these current-state fields.

Useful? React with 👍 / 👎.

Comment on lines 178 to +181
useEffect(() => {
const routedId = surfaceIdForPath(pathname);
if (!routedId || routedId === activeSurfaceId) return;
void host.activateSurface(routedId);
}, [activeSurfaceId, host, pathname]);
if (routedId) void host.activateSurface(routedId);
}, [host, pathname]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep toolbar surface changes synchronized with their route

Selecting a routed surface from MainToolbar still calls only host.activateSurface, while this effect no longer reruns after activeSurfaceId changes. As a result, choosing Workspace, Chat, Documents, Filing, or Cards from the toolbar can render that surface while the URL remains on the prior route; refreshing or sharing the URL then opens a different surface. Route toolbar selections through router.push (or retain reconciliation when the active surface changes).

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the Console Goal Stack’s reconciliation and promotion pipeline by making plan polling more resilient (especially under degraded event streams), projecting additional task lifecycle detail (escalations, assigned heads, changed paths), and ensuring “Save as program” produces an explicitly advisory, non-side-effecting programmable graph.

Changes:

  • Extend plan/task projection to support durable escalations, assigned heads, more complete changed-path ingestion, and better event-to-task targeting/canonicalization.
  • Make plan polling cursor behavior robust when what_changed is degraded, while preserving event history and task change history across inspect snapshots.
  • Update Console Goal Stack UI/UX (responsive resizable panes, additional task badges/inspector details) and harden tests (unit + route + e2e) around these behaviors.

Reviewed changes

Copilot reviewed 18 out of 22 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/theorem-acp/src/plan-state.ts Adds escalated status and escalation/assigned-head projection; improves event normalization, cursor handling, and history merging across snapshots.
packages/theorem-acp/src/plan-program.ts Makes promoted programs advisory (authority: advisory), non-side-effecting, and records side-effecting affordances in metadata only.
apps/console/src/views/goal-stack/ToolPalette.tsx Adds a stable data-plan-capability attribute to support reliable e2e DnD selection.
apps/console/src/views/goal-stack/PromotionDialog.tsx Resets dialog state via keyed remount and surfaces an advisory warning for side-effecting source affordances.
apps/console/src/views/goal-stack/ProgressEdge.tsx Treats escalated edges as warn-color and renders the running overlay consistently (incl. 0 progress).
apps/console/src/views/goal-stack/ProgressEdge.test.tsx Adds coverage for “running with 0 progress” rendering behavior.
apps/console/src/views/goal-stack/PlanTaskNode.tsx Tightens droppable eligibility and adds badges for approval requirement, escalation target, and changed-path presence.
apps/console/src/views/goal-stack/PlanTaskNode.test.tsx Adds coverage for rendering escalation target details on nodes.
apps/console/src/views/goal-stack/plan-layout.ts Adds escalated as a projected edge state.
apps/console/src/views/goal-stack/NodeInspector.tsx Shows assigned head + escalation detail; improves revert/attachment gating and adds “replan subtree” affordance.
apps/console/src/views/goal-stack/GoalStackView.tsx Introduces resizable responsive panes and ensures pins load synchronously when switching plans.
apps/console/src/motion/motion.css Scales edge animation timing with motion variables.
apps/console/src/lib/plan-route.test.ts Adds route-level tests for refusal mapping, advisory promotion behavior, and degraded-cursor semantics.
apps/console/src/lib/goal-stack-contract.test.ts Adds contract-level tests for event folding, degraded cursor behavior, escalation projection, and advisory promotion metadata.
apps/console/src/components/shell/Sidebar.tsx Improves collapse behavior with compact mode and async write-through handling.
apps/console/src/components/shell/IntuiShell.tsx Simplifies route-driven surface activation behavior.
apps/console/src/app/api/harness/plan/route.ts Ensures cursor stability under degraded events, always refreshes runs rail, demotes side-effecting affordances to metadata, and expands refusal envelope parsing.
apps/console/e2e/workspace-goal-stack.spec.ts Makes surface navigation assertions more robust; stabilizes Goal Stack open+DnD flows across viewports and adds new UI assertions/screenshots.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/console/src/views/goal-stack/NodeInspector.tsx (1)

173-208: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep subtree replanning limited to blocked tasks.

Lines 189-196 are still inside taskRevertEligible, which means failed and superseded tasks retain replan_subtree. Remove that button from this fragment and leave the blocked-only branch.

Proposed fix
-            <button
-              type="button"
-              disabled={busy}
-              onClick={() => mutate('replan_subtree', { taskId: task.id })}
-              className="h-ij-control rounded-ij-arc bg-ij-accent px-3 text-ij-ink-bright"
-            >
-              Replan subtree
-            </button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/views/goal-stack/NodeInspector.tsx` around lines 173 - 208,
Remove the “Replan subtree” button from the taskRevertEligible fragment, leaving
only the revert controls there. Keep subtree replanning available exclusively
through the existing task.status === 'blocked' branch.
🧹 Nitpick comments (1)
apps/console/src/app/api/harness/plan/route.ts (1)

35-50: 🚀 Performance & Scalability | 🔵 Trivial

Runs rail progress query now fires unconditionally on every poll tick.

The runsRail query const runsRail = await callHarnessMcp('plan', { action: 'query', plan_id: planId, query: 'progress' }); is no longer gated behind includeManifest the way the capability manifest fetch is (that one is gated and only re-fetched once per session via manifestLoadedRef). Since the Goal Stack view polls this route roughly every 1.5s per open plan session, this adds one more recurring upstream MCP round trip per poll tick, per open session, indefinitely. If this endpoint becomes a hot path with several concurrently open plans, consider throttling/caching the progress query similarly to how the manifest fetch is gated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/app/api/harness/plan/route.ts` around lines 35 - 50, The
plan polling route unconditionally calls the runsRail progress query on every
poll. Update the runsRail flow around callHarnessMcp and
normalizeRunsFromProgress to gate or cache progress retrieval, using the
existing manifestLoadedRef/session-fetch pattern or an equivalent throttle so
repeated polls do not issue an upstream request indefinitely while preserving
the current runsRail response behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/console/e2e/workspace-goal-stack.spec.ts`:
- Around line 45-53: Update openGoalPlan so its fast path verifies that the
currently rendered plan matches the requested planId before skipping the open
flow. Read the visible plan’s ID or another plan-specific invariant, and only
reuse the existing surface when it matches; otherwise fill the Plan id input and
click Open, then retain the final task visibility assertion.

In `@apps/console/src/components/shell/Sidebar.tsx`:
- Around line 200-206: Update toggleCollapse in Sidebar so it returns
immediately when compact mode is active, matching the footer action’s disabled
behavior. Ensure Cmd/Ctrl+B does not call setCollapseOverride or persist the
collapsed update while compact, while preserving the existing toggle behavior in
non-compact mode and updating the callback dependencies as needed.

In `@apps/console/src/views/goal-stack/GoalStackView.tsx`:
- Around line 65-68: Update openPlan and the other plan-switching paths to clear
all plan-bound state—snapshot, selected task, nodes, and capabilities—before
changing the active plan via setPlanId. Then load pins for the new plan and
preserve the empty-map behavior when no plan is selected, preventing stale
interactions during polling.

In `@packages/theorem-acp/src/plan-state.ts`:
- Around line 239-254: Update the snapshot merge flow around
mergeSnapshotHistory so stale inspected snapshots do not overwrite
previous.status or previous.claimHolder when inspected.streamCursor is less than
previous.streamCursor. Preserve the inspected snapshot’s status and claimHolder
when its cursor is equal to or newer than previous.streamCursor, while leaving
event folding and cursor calculation unchanged.

---

Outside diff comments:
In `@apps/console/src/views/goal-stack/NodeInspector.tsx`:
- Around line 173-208: Remove the “Replan subtree” button from the
taskRevertEligible fragment, leaving only the revert controls there. Keep
subtree replanning available exclusively through the existing task.status ===
'blocked' branch.

---

Nitpick comments:
In `@apps/console/src/app/api/harness/plan/route.ts`:
- Around line 35-50: The plan polling route unconditionally calls the runsRail
progress query on every poll. Update the runsRail flow around callHarnessMcp and
normalizeRunsFromProgress to gate or cache progress retrieval, using the
existing manifestLoadedRef/session-fetch pattern or an equivalent throttle so
repeated polls do not issue an upstream request indefinitely while preserving
the current runsRail response behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 95d9caa4-c4ae-44b2-9f98-2daf41ad0808

📥 Commits

Reviewing files that changed from the base of the PR and between c610b9f and 5922175.

⛔ Files ignored due to path filters (4)
  • apps/console/e2e/workspace-goal-stack.spec.ts-snapshots/goal-stack-v2-1024-dark-darwin.png is excluded by !**/*.png
  • apps/console/e2e/workspace-goal-stack.spec.ts-snapshots/goal-stack-v2-1440-dark-darwin.png is excluded by !**/*.png
  • apps/console/e2e/workspace-goal-stack.spec.ts-snapshots/goal-stack-v2-390-dark-darwin.png is excluded by !**/*.png
  • apps/console/e2e/workspace-goal-stack.spec.ts-snapshots/workspace-substrate-v2-1440-dark-darwin.png is excluded by !**/*.png
📒 Files selected for processing (18)
  • apps/console/e2e/workspace-goal-stack.spec.ts
  • apps/console/src/app/api/harness/plan/route.ts
  • apps/console/src/components/shell/IntuiShell.tsx
  • apps/console/src/components/shell/Sidebar.tsx
  • apps/console/src/lib/goal-stack-contract.test.ts
  • apps/console/src/lib/plan-route.test.ts
  • apps/console/src/motion/motion.css
  • apps/console/src/views/goal-stack/GoalStackView.tsx
  • apps/console/src/views/goal-stack/NodeInspector.tsx
  • apps/console/src/views/goal-stack/PlanTaskNode.test.tsx
  • apps/console/src/views/goal-stack/PlanTaskNode.tsx
  • apps/console/src/views/goal-stack/ProgressEdge.test.tsx
  • apps/console/src/views/goal-stack/ProgressEdge.tsx
  • apps/console/src/views/goal-stack/PromotionDialog.tsx
  • apps/console/src/views/goal-stack/ToolPalette.tsx
  • apps/console/src/views/goal-stack/plan-layout.ts
  • packages/theorem-acp/src/plan-program.ts
  • packages/theorem-acp/src/plan-state.ts

Comment on lines +45 to +53
async function openGoalPlan(page: Page, planId: string) {
const activeSurface = await page.locator('[data-shell]').getAttribute('data-active-surface');
if (activeSurface !== 'console-goals') await openSurface(page, 'console-goals');
const firstTask = page.locator('[data-plan-task]').first();
if (!await firstTask.isVisible().catch(() => false)) {
await page.locator('input[aria-label="Plan id"]').fill(planId);
await page.getByRole('button', { name: 'Open' }).click();
}
await expect(firstTask).toBeVisible();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Match the fast path to the requested plan.

When any task is visible, firstTask.isVisible() is true and planId is ignored. A different or stale plan can therefore remain open while the test proceeds. Compare the rendered plan ID, or another plan-specific invariant, before skipping the open flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/e2e/workspace-goal-stack.spec.ts` around lines 45 - 53, Update
openGoalPlan so its fast path verifies that the currently rendered plan matches
the requested planId before skipping the open flow. Read the visible plan’s ID
or another plan-specific invariant, and only reuse the existing surface when it
matches; otherwise fill the Plan id input and click Open, then retain the final
task visibility assertion.

Comment on lines 200 to 206
const toggleCollapse = useCallback(() => {
if (!landmarksRegion) return;
const next = !collapsed;
setCollapsed(next);
void host.emit({ kind: 'update', id: landmarksRegion.object.id, patch: { collapsed: next } });
setCollapseOverride(next);
void host.emit({ kind: 'update', id: landmarksRegion.object.id, patch: { collapsed: next } })
.finally(() => setCollapseOverride(null));
}, [collapsed, host, landmarksRegion]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Ignore the collapse shortcut in compact mode.

The footer disables this action when compact, but Cmd/Ctrl+B still persists collapsed. Returning to a wider layout can therefore leave the sidebar unexpectedly collapsed.

Proposed fix
 const toggleCollapse = useCallback(() => {
-  if (!landmarksRegion) return;
+  if (compact || !landmarksRegion) return;
   const next = !collapsed;
   setCollapseOverride(next);
   void host.emit({ kind: 'update', id: landmarksRegion.object.id, patch: { collapsed: next } })
     .finally(() => setCollapseOverride(null));
-}, [collapsed, host, landmarksRegion]);
+}, [collapsed, compact, host, landmarksRegion]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const toggleCollapse = useCallback(() => {
if (!landmarksRegion) return;
const next = !collapsed;
setCollapsed(next);
void host.emit({ kind: 'update', id: landmarksRegion.object.id, patch: { collapsed: next } });
setCollapseOverride(next);
void host.emit({ kind: 'update', id: landmarksRegion.object.id, patch: { collapsed: next } })
.finally(() => setCollapseOverride(null));
}, [collapsed, host, landmarksRegion]);
const toggleCollapse = useCallback(() => {
if (compact || !landmarksRegion) return;
const next = !collapsed;
setCollapseOverride(next);
void host.emit({ kind: 'update', id: landmarksRegion.object.id, patch: { collapsed: next } })
.finally(() => setCollapseOverride(null));
}, [collapsed, compact, host, landmarksRegion]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/shell/Sidebar.tsx` around lines 200 - 206, Update
toggleCollapse in Sidebar so it returns immediately when compact mode is active,
matching the footer action’s disabled behavior. Ensure Cmd/Ctrl+B does not call
setCollapseOverride or persist the collapsed update while compact, while
preserving the existing toggle behavior in non-compact mode and updating the
callback dependencies as needed.

Comment on lines +65 to +68
const openPlan = useCallback((nextPlanId: string) => {
setPlanId(nextPlanId);
setPins(nextPlanId ? loadPins(nextPlanId) : new Map());
}, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reset plan-bound state before changing the active plan.

planId changes immediately, but the prior snapshot, selected task, nodes, and capabilities remain interactive until the new poll resolves. An action in that window posts the old task ID to the new plan.

Proposed fix
 const openPlan = useCallback((nextPlanId: string) => {
+  setSnapshot(null);
+  setCapabilities([]);
+  setNodes([]);
+  setEdges([]);
+  setSelectedTaskId(null);
+  setPromotionOpen(false);
   setPlanId(nextPlanId);
   setPins(nextPlanId ? loadPins(nextPlanId) : new Map());
 }, []);

Also applies to: 189-194, 211-214

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/views/goal-stack/GoalStackView.tsx` around lines 65 - 68,
Update openPlan and the other plan-switching paths to clear all plan-bound
state—snapshot, selected task, nodes, and capabilities—before changing the
active plan via setPlanId. Then load pins for the new plan and preserve the
empty-map behavior when no plan is selected, preventing stale interactions
during polling.

Comment on lines +239 to +254
const eventStreamDegraded = record(payload.degraded)?.events === true;
const base = inspected ? mergeSnapshotHistory(inspected, previous) : previous;
if (!base) throw new Error('Plan stream response did not contain a usable snapshot.');
const foldEvents = inspected
? events.filter((event) => event.graphVersion > inspected.streamCursor)
: events;
const snapshot = foldEvents.reduce(applyPlanEvent, base);

const explicitCursor = number(payload.cursor);
const cursor = eventStreamDegraded
? Math.max(previous?.streamCursor ?? 0, explicitCursor ?? previous?.streamCursor ?? 0)
: Math.max(
snapshot.streamCursor,
explicitCursor ?? 0,
...events.map((event) => event.graphVersion),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'plan-state\.ts$' . || true

echo "== file stats =="
stat -c '%n %s' packages/theorem-acp/src/plan-state.ts || true
wc -l packages/theorem-acp/src/plan-state.ts

echo "== relevant outlines =="
ast-grep outline packages/theorem-acp/src/plan-state.ts --view expanded || true

echo "== relevant snippets =="
sed -n '200,275p' packages/theorem-acp/src/plan-state.ts
echo "-----"
sed -n '430,505p' packages/theorem-acp/src/plan-state.ts

echo "== search mergeSnapshotHistory and related usages =="
rg -n "mergeSnapshotHistory|foldEvents|streamCursor|applyPlanEvent|what_changed|degraded|status|claimHolder" packages/theorem-acp/src/plan-state.ts

Repository: Travis-Gilbert/CommonPlace

Length of output: 13016


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tests mentioning normalizePlanPoll / mergeSnapshotHistory / stale cursor / what_changed =="
rg -n "normalizePlanPoll|mergeSnapshotHistory|what_changed|streamCursor|status|claimHolder|stale" packages/theorem-acp -g '*.ts' -g '*.js' || true

echo "== test files around plan-state =="
fd -a '.*test.*|.*spec.*' packages/theorem-acp || true

echo "== inspect tests for plan-state =="
rg -n "describe|it\\(|test\\(|normalizePlanPoll|streamCursor|status|claimHolder|degraded" packages/theorem-acp --glob '*.test.ts' --glob '*.spec.ts' --glob '*.test.js' --glob '*.spec.js' || true

Repository: Travis-Gilbert/CommonPlace

Length of output: 7031


Guard stale status/claimHolder when merging snapshots across polls.

When inspected.streamCursor < previous.streamCursor, mergeSnapshotHistory still overwrites status and claimHolder from the fresher payload while skipping previously folded events, so task state can regress after an advanced cursor has already folded a later event. Use previous for these fields when the inspected cursor is stale; preserve status/claimHolder authoritative when cursors are equal or newer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/theorem-acp/src/plan-state.ts` around lines 239 - 254, Update the
snapshot merge flow around mergeSnapshotHistory so stale inspected snapshots do
not overwrite previous.status or previous.claimHolder when
inspected.streamCursor is less than previous.streamCursor. Preserve the
inspected snapshot’s status and claimHolder when its cursor is equal to or newer
than previous.streamCursor, while leaving event folding and cursor calculation
unchanged.

@Travis-Gilbert
Travis-Gilbert merged commit 5509ff7 into main Jul 23, 2026
3 of 5 checks passed
Travis-Gilbert added a commit that referenced this pull request Jul 23, 2026
Narrow harness boot MCP error access, clear set-state-in-effect lint,
brace broken Playwright evaluate callbacks, and loosen jotai facade
typing so console build and gates can pass on main.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants