Skip to content

feat(web): loop node iteration visibility in workflow execution view#1026

Merged
coleam00 merged 8 commits intodevfrom
archon/task-fix-issue-1014
Apr 10, 2026
Merged

feat(web): loop node iteration visibility in workflow execution view#1026
coleam00 merged 8 commits intodevfrom
archon/task-fix-issue-1014

Conversation

@coleam00
Copy link
Copy Markdown
Owner

@coleam00 coleam00 commented Apr 9, 2026

Summary

  • Problem: Loop nodes in DAG workflows were invisible in the Web UI. loop_iteration_* events were stored in the DB and emitted over SSE, but the frontend silently dropped them (no workflow_step case in useSSE.ts, no iteration fields in DagNodeState).
  • Why it matters: Users running long-running loop workflows had no way to tell how many iterations had run or which was active, making progress monitoring impossible without checking server logs.
  • What changed: Wired the existing loop_iteration_* data pipeline end-to-end — SSE bridge through store to UI components — adding live iteration badges on graph nodes and an expandable per-iteration sub-list in the Logs tab sidebar.
  • What did not change: No backend schema, executor, database migration, or approval/gate UI changes. All iteration data was already present in the DB and SSE stream.

UX Journey

Before

User                      Web UI (Graph tab)         Web UI (Logs tab)
----                      ------------------         -----------------
starts loop workflow ---> * LOOP   implement         * implement    2.3m
                           (looks like PROMPT)         (flat, no expand)
                           No iteration count          No sub-list

After

User                      Web UI (Graph tab)         Web UI (Logs tab)
----                      ------------------         -----------------
starts loop workflow ---> * LOOP   implement  3/20   > * implement  3/20  2.3m
                                                        (expandable)
clicks expand ----------------------------------------> v * implement  3/20  2.3m
                                                            v Iteration 1   1.2s
                                                            v Iteration 2   0.9s
                                                            * Iteration 3        <- running

Architecture Diagram

Before

workflow-bridge.ts          useSSE.ts                workflow-store.ts
------------------          ---------                -----------------
loop_iteration_started  --> case 'workflow_step':    (no handler)
loop_iteration_completed    [MISSING - dropped]
loop_iteration_failed

After

workflow-bridge.ts [~]      useSSE.ts [~]            workflow-store.ts [~]     DagNodeState [~]
------------------          ---------                -----------------         ----------------
loop_iteration_started  --> case 'workflow_step': -> handleLoopIteration() --> currentIteration [+]
  + nodeId [+]               onLoopIteration() [+]   updates per-node          maxIterations [+]
loop_iteration_completed                              iteration array           iterations[] [+]
  + nodeId [+]
loop_iteration_failed
  + nodeId [+]

                       --> ExecutionDagNode.tsx [~]: "N/M" badge on graph node
                       --> DagNodeProgress.tsx [~]:  expandable iteration sub-list

REST path (historical):
WorkflowExecution.tsx [~] --> loop_iteration_* events --> nodeMap enrichment --> same UI

Connection inventory:

From To Status Notes
workflow-bridge.ts SSE workflow_step payload modified Added nodeId field to all 3 loop iteration cases
useSSE.ts workflow-store.ts handleLoopIteration new New workflow_step case dispatches to store
workflow-store.ts DagNodeState.iterations new Per-node iteration array tracking
WorkflowExecution.tsx DagNodeState (REST path) modified Second pass enriches nodes from loop_iteration_* events
DagNodeState DagNodeProgress.tsx modified Renders expandable iteration sub-list
DagNodeState ExecutionDagNode.tsx modified Renders "N/M" badge on graph node
WorkflowDagViewer.tsx ExecutionDagNode.tsx modified Passes currentIteration/maxIterations to node data

Label Snapshot

  • Risk: risk: low
  • Size: size: M
  • Scope: server, web
  • Module: server:workflow-bridge, web:workflow-store, web:useSSE, web:DagNodeProgress, web:ExecutionDagNode

Change Metadata

  • Change type: feature
  • Primary scope: web

Linked Issue

Validation Evidence (required)

bun run validate
  • bun run type-check: All 9 packages pass (git, paths, isolation, workflows, core, cli, adapters, web, server)
  • bun run lint --max-warnings 0: 0 errors, 0 warnings
  • bun run format:check: All files formatted correctly
  • bun run test: All packages pass, 0 failures

One deviation from the plan: the data as LoopIterationEvent cast in useSSE.ts was removed after ESLint flagged it as unnecessary — the switch-case discriminant already narrows data to LoopIterationEvent automatically.

Security Impact (required)

  • New permissions/capabilities? No
  • New external network calls? No
  • Secrets/tokens handling changed? No
  • File system access scope changed? No

Compatibility / Migration

  • Backward compatible? Yes — existing workflow runs without loop_iteration_* events display normally; new UI paths are guarded by node.iterations?.length > 0
  • Config/env changes? No
  • Database migration needed? No — all iteration data was already stored in remote_agent_workflow_events.data

Human Verification (required)

  • Verified scenarios: Full bun run validate suite (type-check, lint, format, tests) passes with exit 0
  • Edge cases checked: nodeId missing on SSE event (early return in store), total: 0 on completed events (preserves maxIterations from prior started event), non-loop nodes unaffected
  • What was not verified: Live browser UI test with a running loop workflow (requires running server + a loop workflow definition)

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: DAG workflows with loop: nodes in the Web UI only
  • Potential unintended effects: None — all new rendering paths are guarded; non-loop nodes have no iterations data
  • Guardrails/monitoring for early detection: Type system enforces LoopIterationInfo shape consistency across store, REST extraction, and components

Rollback Plan (required)

  • Fast rollback command/path: git revert HEAD — all changes are purely additive (new fields, new cases, new rendering) with no schema changes
  • Feature flags or config toggles: None needed; iteration UI is invisible for runs without loop_iteration_* events
  • Observable failure symptoms: Loop nodes show no iteration badge or expand toggle; workflow_step SSE events silently dropped (same behavior as before the fix)

Risks and Mitigations

  • Risk: Expand toggle click and node-selection click interact unexpectedly
    • Mitigation: e.stopPropagation() on the expand button prevents triggering onNodeClick
  • Risk: workflow_step SSE events for non-DAG loops (no nodeId) causing store errors
    • Mitigation: handleLoopIteration early-returns when event.nodeId is falsy

Summary by CodeRabbit

  • New Features

    • Loop nodes now show iteration counts (current/max), expandable per-iteration rows with status and duration, and click/toggle behavior that won’t trigger parent node actions.
    • Live updates surface per-iteration running/completed/failed states and preserve iteration history after node completion.
  • Bug Fixes

    • Deduplicate streamed assistant text against stored messages to avoid duplicates.
    • Avoid re-sending pre-command explanation text during stream-mode workflow invocations.

…#1014)

Loop nodes in DAG workflows appeared as flat nodes with no iteration visibility.
The backend already emitted loop_iteration_* events but the frontend dropped them.

Changes:
- Add nodeId to workflow_step SSE events in workflow-bridge
- Add LoopIterationEvent/LoopIterationInfo types and extend DagNodeState
- Handle workflow_step events in useSSE and route to store
- Add handleLoopIteration action to workflow store
- Extract loop_iteration_* events for historical REST view
- Add expandable iteration sub-list in DagNodeProgress sidebar
- Add iteration count badge on graph nodes (ExecutionDagNode)
- Add loop type color/label to graph node type maps

Fixes #1014

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Adds per-loop-iteration visibility: server now includes nodeId on loop iteration SSE payloads; SSE handling, types, and store actions ingest workflow_step loop iteration events; frontend enriches DAG node state and renders expandable per-iteration rows and live iteration counters.

Changes

Cohort / File(s) Summary
Server SSE payload
packages/server/src/adapters/web/workflow-bridge.ts
Include nodeId in workflow_step payloads for loop_iteration_started/_completed/_failed; update inline comment reference.
SSE types & handlers
packages/web/src/lib/types.ts, packages/web/src/hooks/useSSE.ts, packages/web/src/hooks/useDashboardSSE.ts
Add LoopIterationInfo/LoopIterationEvent types; extend SSEEvent union; add onLoopIteration handler and route workflow_step events to it.
Store & tests
packages/web/src/stores/workflow-store.ts, packages/web/src/stores/workflow-store.test.ts
Add handleLoopIteration action to upsert per-node iterations, set currentIteration/maxIterations, preserve iteration state across node updates; add tests covering upsert, preservation, and no-op cases.
Client event enrichment
packages/web/src/components/workflows/WorkflowExecution.tsx
Second-pass over data.events to enrich dagNodes with iterations, currentIteration, and maxIterations from loop_iteration_* events.
Dag node data & viewer
packages/web/src/components/workflows/ExecutionDagNode.tsx, packages/web/src/components/workflows/WorkflowDagViewer.tsx
Add optional currentIteration/maxIterations to ExecutionNodeData; add loop label/color; viewer passes live iteration metadata into node data.
UI: node progress & interaction
packages/web/src/components/workflows/DagNodeProgress.tsx
Introduce DagNodeItem wrapper with per-node expanded state; caret toggles to expand per-iteration rows; show currentIteration/maxIterations; stop toggle propagation to parent clicks.
Logs, streaming, and orchestrator tweaks
packages/web/src/components/workflows/WorkflowLogs.tsx, packages/workflows/src/dag-executor.ts, packages/core/src/orchestrator/orchestrator-agent.ts
Deduplicate DB vs SSE assistant messages (exact/startsWith); treat certain status emojis as message boundaries; stream tool-call sends include { category: 'tool_call_formatted' }; prevent duplicate pre-command streaming text on workflow invocation.
Other
packages/web/src/components/workflows/WorkflowDagViewer.tsx
Pass live iteration metadata into rendered node data (small connector change).

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Server as Server (workflow-bridge)
participant SSE as SSE stream
participant Client as Web client (useSSE / useDashboardSSE)
participant Store as workflow-store
participant UI as WorkflowExecution / Dag Components

Server->>SSE: emit `workflow_step` (loop_iteration_* with `nodeId`, `iteration`, `total`, `status`)
SSE->>Client: push SSE event
Client->>Store: call onLoopIteration(parsed LoopIterationEvent)
Store->>Store: upsert iteration in `dagNodes[].iterations`, set `currentIteration`/`maxIterations`
Store-->>UI: state update (dagNodes with iterations/currentIteration)
UI->>UI: render node badge and expandable iteration rows

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Round and round the loop I hop,

Counting laps and never stop.
Iterations lined in tidy rows,
Each small step the progress shows.
Hooray — the workflow garden grows!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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
Title check ✅ Passed The title 'feat(web): loop node iteration visibility in workflow execution view' accurately describes the main feature — enabling UI visibility for loop node iterations in the execution view.
Description check ✅ Passed The description thoroughly covers all template sections: problem statement, UX journey diagrams (before/after), architecture changes, connection inventory, labels, linked issues, validation evidence, security/compatibility analysis, human verification, and rollback plan.
Linked Issues check ✅ Passed The PR successfully implements all technical objectives from #1014: iteration count display on graph nodes, expandable iteration lists in logs, live progress tracking, per-iteration event grouping, and SSE event wiring—all without backend schema changes.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objectives: SSE bridge wiring, store handlers, type definitions, UI component updates, and REST-path enrichment for loop iteration visibility. Changes to orchestrator-agent.ts and WorkflowLogs.tsx are directly supporting the core iteration display functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 archon/task-fix-issue-1014

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 and usage tips.

@coleam00
Copy link
Copy Markdown
Owner Author

coleam00 commented Apr 9, 2026

Comprehensive PR Review — 4 Agents

PR: #1026 — feat(web): loop node iteration visibility in workflow execution view
Reviewed by: code-review, error-handling, test-coverage, comment-quality agents
Date: 2026-04-09


Summary

Solid scope discipline and correct type design. Two HIGH bugs need fixes before merge.

Verdict: REQUEST_CHANGES

Severity Count
HIGH 2
MEDIUM 3
LOW 4

HIGH — handleDagNode drops iteration state on node completion

Location: packages/web/src/stores/workflow-store.ts:249-259

handleDagNode builds a fresh DagNodeState without spreading the existing node. When node_completed/node_failed arrives for a loop node, all iterations, currentIteration, and maxIterations from prior handleLoopIteration calls are silently discarded. The iteration badge and expandable sub-list disappear the moment the loop finishes — exactly when users are most likely to inspect them.

Fix — spread existing node first (same pattern already used in handleLoopIteration lines 294-299):

const nodeState: DagNodeState = {
  ...(existingIdx >= 0 ? dagNodes[existingIdx] : {}),  // preserve accumulated state
  nodeId: event.nodeId,
  name: event.name,
  status: event.status,
  duration: event.duration,
  error: event.error,
  reason: event.reason,
};

HIGH — Nested <button> inside <button> — HTML spec violation

Location: packages/web/src/components/workflows/DagNodeProgress.tsx:35

The expand/collapse toggle is a <button> nested inside the row-selection <button>. HTML forbids interactive content inside interactive content. Browsers silently split the outer button at parse time — e.stopPropagation() then operates on a detached element, so the outer onNodeClick fires on every expand click, switching the Logs panel to the wrong node. Keyboard/AT users also cannot reach the expand control.

Option A — Keep outer <button>, replace inner with <span role="button" tabIndex={0}> plus onKeyDown for Enter/Space.

Option B — Sibling layout: expand button and row button as two separate <button> siblings inside a <div>, no nesting.

Either option eliminates the spec violation. Option B is the cleanest for accessibility.


MEDIUM — if (!iteration) falsy guard silently skips iteration 0

Location: packages/web/src/components/workflows/WorkflowExecution.tsx:147

if (!iteration) continue skips undefined AND 0. Executor is currently 1-based so no visible bug today, but a future 0-based change would silently drop the first iteration.

Fix: if (iteration === undefined) continue or if (typeof iteration !== 'number') continue


MEDIUM — Missing unit tests for handleLoopIteration

Location: packages/web/src/stores/workflow-store.ts:268-309

The core stateful logic (early-return branches, upsert semantics, total: 0 guard) has zero test coverage. workflow-store.test.ts already has all the infrastructure needed. Estimated ~30 min.

Scenarios needed: no-op on missing nodeId, no-op on missing node, append first iteration, upsert on repeated iteration number, preserve maxIterations when total: 0, accumulate multiple iterations.

Full test outline with code is in the test-coverage-findings.md artifact.


MEDIUM — Dead file reference in bridge comment

Location: packages/server/src/adapters/web/workflow-bridge.ts:54 and :70

Both loop_iteration_completed and loop_iteration_failed branches say useWorkflowStatus.ts guards against 0. That file does not exist anywhere in the repo. The actual guard is handleLoopIteration in workflow-store.ts. A developer following this comment will find nothing and may remove the store guard thinking it is handled elsewhere.

Fix both lines to read:
// workflow-store.ts handleLoopIteration guards against 0 by preserving the prior wf.maxIterations value.


LOW Issues

  • WorkflowExecution.tsx:143if (!existing) continue needs a comment explaining this is a safe SSE ordering race (matching the comment already on the equivalent store guard)
  • workflow-store.ts:278 — Early-return comment is correct but terse; expand to clarify the SSE delivery ordering race
  • WorkflowExecution.tsx:137-174 — REST-path loop enrichment has no tests; consider extracting to testable utility (not blocking)
  • useSSE.ts:189-193workflow_step SSE dispatch untested; low priority given TypeScript narrowing

What's Good

  • No backend schema/executor/interactive-loop-gate changes — exactly what the issue requested
  • total: 0 intentional-emission pattern is well documented; store guard event.total > 0 ? event.total : existing.maxIterations is correct
  • Both handleLoopIteration early-return guards carry CLAUDE.md-compliant explanatory comments
  • workflowSSEHandlers stable-ref pattern correctly extended — no new hook instability
  • Zero @archon/workflows imports in @archon/web
  • ESLint deviation (removing unnecessary cast) is the right call

Suggested Follow-up Issues

  • Add unit tests for mapWorkflowEvent loop branches in workflow-bridge.ts (P2)
  • Add unit tests for REST-path loop enrichment in WorkflowExecution.tsx (P3)

Reviewed by Archon comprehensive-pr-review workflow | Artifacts: ~/.archon/workspaces/coleam00/Archon/artifacts/runs/b47b79698dbf3568b70d89ff6ebf0f61/review/

…n, guard, tests

- Preserve accumulated iteration state in handleDagNode by spreading existing node before overwriting with event fields (HIGH: iteration badge/list vanished on node completion)
- Replace nested <button> with <span role="button"> + onKeyDown in DagNodeProgress to fix HTML spec violation and broken stopPropagation (HIGH: accessibility + mis-navigation)
- Fix falsy guard `if (!iteration)` → `if (iteration === undefined)` in WorkflowExecution REST enrichment (MEDIUM: would silently drop iteration 0 in future)
- Fix dead file reference in workflow-bridge comments: `useWorkflowStatus.ts` → `workflow-store.ts handleLoopIteration` (MEDIUM: misleading comment)
- Add 7 unit tests for handleLoopIteration in workflow-store.test.ts covering all branches: no-nodeId no-op, ghost-node no-op, first append, upsert, total:0 preservation, accumulation, and iteration state survival across dag_node completion (MEDIUM: zero coverage for core PR logic)
- Clarify two LOW comment gaps in WorkflowExecution.tsx and workflow-store.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coleam00
Copy link
Copy Markdown
Owner Author

coleam00 commented Apr 9, 2026

Fix Report — PR #1026 Review Findings

All 9 review findings addressed in commit c639063f.


HIGH Issues Fixed (2/2)

Issue 1: handleDagNode drops iteration state on node completion
packages/web/src/stores/workflow-store.ts — Added spread of existing node state before overwriting with event fields. Iteration badge/list now persists after the loop node reaches a terminal status.

Issue 2: Nested <button> inside <button> — HTML spec violation
packages/web/src/components/workflows/DagNodeProgress.tsx — Replaced inner <button> with <span role="button" tabIndex={0}> plus onKeyDown handler (Enter/Space). Fixes stopPropagation reliability and keyboard accessibility.


MEDIUM Issues Fixed (3/3)

Issue 3: Falsy guard silently skips iteration 0
WorkflowExecution.tsx:147if (!iteration)if (iteration === undefined)

Issue 4: Missing unit tests for handleLoopIteration
workflow-store.test.ts — Added 7 new tests covering: non-DAG no-op, ghost-node no-op, first-append, upsert-by-iteration, total: 0 maxIterations preservation, multi-iteration accumulation, and iteration state survival across dag_node completion (directly validates Issue 1 fix). All 30 store tests pass.

Issue 5: Dead file reference in bridge comments
workflow-bridge.ts:54,70useWorkflowStatus.tsworkflow-store.ts handleLoopIteration


LOW Issues Fixed (2/2)

  • Added // No node_started event yet — skip (events ordered in DB) to REST enrichment guard in WorkflowExecution.tsx
  • Expanded early-return comment in workflow-store.ts to explain SSE ordering race rationale

Validation

  • bun run type-check — PASS (all packages)
  • bun run lint — PASS (0 warnings)
  • bun run test — PASS (all packages, 30 workflow-store tests)

Deferred as Follow-up Issues

  • mapWorkflowEvent test file (workflow-bridge.test.ts) — pure function, needs new batch in server package.json
  • REST-path enrichment unit test — needs extraction to pure utility function first

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/server/src/adapters/web/workflow-bridge.ts (1)

63-75: ⚠️ Potential issue | 🟡 Minor

Include duration on failed loop iteration events.

LoopIterationFailedEvent already carries duration, but this payload drops it. That means failed iterations lose their elapsed time in the live UI even though the executor emitted it.

Suggested fix
     case 'loop_iteration_failed':
       return JSON.stringify({
         type: 'workflow_step',
         runId: event.runId,
         nodeId: event.nodeId,
         step: event.iteration - 1,
         // total: 0 intentionally — maxIterations is not carried by loop_iteration_completed/failed events.
         // workflow-store.ts handleLoopIteration guards against 0 by preserving the prior wf.maxIterations value.
         total: 0,
         name: `iteration-${String(event.iteration)}`,
         status: 'failed',
+        duration: event.duration,
         iteration: event.iteration,
         timestamp: Date.now(),
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/adapters/web/workflow-bridge.ts` around lines 63 - 75,
The 'loop_iteration_failed' case in workflow-bridge.ts drops the event.duration
even though LoopIterationFailedEvent contains it; update the JSON payload
returned in the 'loop_iteration_failed' branch to include duration:
event.duration (preserving the numeric value) so failed loop iterations carry
elapsed time into the live UI—locate the case 'loop_iteration_failed' in the
switch that returns the workflow_step object and add the duration property to
that returned object.
🧹 Nitpick comments (1)
packages/web/src/components/workflows/DagNodeProgress.tsx (1)

80-92: Minor: redundant nullish coalescing after guard.

The hasIterations check on line 80 already ensures node.iterations is non-empty, making the ?? [] fallback on line 82 defensive but redundant.

🔧 Optional simplification
       {expanded && hasIterations && (
         <div className="ml-6 mt-0.5 space-y-0.5">
-          {(node.iterations ?? []).map(iter => (
+          {node.iterations!.map(iter => (
             <div key={iter.iteration} className="flex items-center gap-2 px-2 py-1 text-xs">

Alternatively, keep the ?? [] as a defensive pattern if you prefer avoiding non-null assertions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/components/workflows/DagNodeProgress.tsx` around lines 80 -
92, The nullish fallback (?? []) in the map is redundant because the surrounding
guard uses hasIterations (and expanded) to ensure node.iterations exists; remove
the fallback from the map expression so the code uses (node.iterations.map(...))
directly. Update the JSX block that renders iterations (the conditional using
expanded && hasIterations and the mapping over node.iterations) to eliminate the
unnecessary ?? [], keeping StatusIcon, formatDurationMs and the existing
key/fields unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/components/workflows/WorkflowExecution.tsx`:
- Around line 145-163: The code is reading loop iteration duration from the
wrong field (e.data.duration_ms) causing durations to be lost on reload; update
the duration read to use e.data.duration (and search for any other occurrences
of duration_ms in this file) so LoopIterationInfo.duration is populated from
e.data.duration wherever loop iteration events are parsed (e.g., the block
building iterState from e.data.duration_ms).

---

Outside diff comments:
In `@packages/server/src/adapters/web/workflow-bridge.ts`:
- Around line 63-75: The 'loop_iteration_failed' case in workflow-bridge.ts
drops the event.duration even though LoopIterationFailedEvent contains it;
update the JSON payload returned in the 'loop_iteration_failed' branch to
include duration: event.duration (preserving the numeric value) so failed loop
iterations carry elapsed time into the live UI—locate the case
'loop_iteration_failed' in the switch that returns the workflow_step object and
add the duration property to that returned object.

---

Nitpick comments:
In `@packages/web/src/components/workflows/DagNodeProgress.tsx`:
- Around line 80-92: The nullish fallback (?? []) in the map is redundant
because the surrounding guard uses hasIterations (and expanded) to ensure
node.iterations exists; remove the fallback from the map expression so the code
uses (node.iterations.map(...)) directly. Update the JSX block that renders
iterations (the conditional using expanded && hasIterations and the mapping over
node.iterations) to eliminate the unnecessary ?? [], keeping StatusIcon,
formatDurationMs and the existing key/fields unchanged.
🪄 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

Run ID: 05175123-faa4-4eee-b226-5b3324b9acd8

📥 Commits

Reviewing files that changed from the base of the PR and between 95679fa and a248438.

📒 Files selected for processing (9)
  • packages/server/src/adapters/web/workflow-bridge.ts
  • packages/web/src/components/workflows/DagNodeProgress.tsx
  • packages/web/src/components/workflows/ExecutionDagNode.tsx
  • packages/web/src/components/workflows/WorkflowDagViewer.tsx
  • packages/web/src/components/workflows/WorkflowExecution.tsx
  • packages/web/src/hooks/useSSE.ts
  • packages/web/src/lib/types.ts
  • packages/web/src/stores/workflow-store.test.ts
  • packages/web/src/stores/workflow-store.ts

Comment on lines +145 to +163
const iteration = e.data.iteration as number | undefined;
const maxIter = e.data.maxIterations as number | undefined;
if (iteration === undefined) continue;

let iterStatus: LoopIterationInfo['status'];
if (e.event_type === 'loop_iteration_started') {
iterStatus = 'running';
} else if (e.event_type === 'loop_iteration_completed') {
iterStatus = 'completed';
} else {
iterStatus = 'failed';
}

const existingIters: LoopIterationInfo[] = existing.iterations ?? [];
const iterIdx = existingIters.findIndex(it => it.iteration === iteration);
const iterState: LoopIterationInfo = {
iteration,
status: iterStatus,
duration: e.data.duration_ms as number | undefined,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Read the persisted loop duration field here.

The executor stores loop iteration elapsed time under data.duration, not data.duration_ms. With the current key, every historical iteration reloaded from REST gets duration: undefined, so iteration timing disappears after a refresh. The same field name should be aligned anywhere else this file reads loop iteration durations.

Suggested fix
               const iterState: LoopIterationInfo = {
                 iteration,
                 status: iterStatus,
-                duration: e.data.duration_ms as number | undefined,
+                duration: e.data.duration as number | undefined,
               };
📝 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 iteration = e.data.iteration as number | undefined;
const maxIter = e.data.maxIterations as number | undefined;
if (iteration === undefined) continue;
let iterStatus: LoopIterationInfo['status'];
if (e.event_type === 'loop_iteration_started') {
iterStatus = 'running';
} else if (e.event_type === 'loop_iteration_completed') {
iterStatus = 'completed';
} else {
iterStatus = 'failed';
}
const existingIters: LoopIterationInfo[] = existing.iterations ?? [];
const iterIdx = existingIters.findIndex(it => it.iteration === iteration);
const iterState: LoopIterationInfo = {
iteration,
status: iterStatus,
duration: e.data.duration_ms as number | undefined,
const iteration = e.data.iteration as number | undefined;
const maxIter = e.data.maxIterations as number | undefined;
if (iteration === undefined) continue;
let iterStatus: LoopIterationInfo['status'];
if (e.event_type === 'loop_iteration_started') {
iterStatus = 'running';
} else if (e.event_type === 'loop_iteration_completed') {
iterStatus = 'completed';
} else {
iterStatus = 'failed';
}
const existingIters: LoopIterationInfo[] = existing.iterations ?? [];
const iterIdx = existingIters.findIndex(it => it.iteration === iteration);
const iterState: LoopIterationInfo = {
iteration,
status: iterStatus,
duration: e.data.duration as number | undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/components/workflows/WorkflowExecution.tsx` around lines 145
- 163, The code is reading loop iteration duration from the wrong field
(e.data.duration_ms) causing durations to be lost on reload; update the duration
read to use e.data.duration (and search for any other occurrences of duration_ms
in this file) so LoopIterationInfo.duration is populated from e.data.duration
wherever loop iteration events are parsed (e.g., the block building iterState
from e.data.duration_ms).

coleam00 and others added 2 commits April 9, 2026 18:05
…ve element

- Add missing `workflow_step` case in useDashboardSSE switch so loop iteration
  events are routed to the store instead of silently dropped
- Restructure DagNodeItem: replace outer <button> with <div role="row"> and
  change the expand toggle from <span role="button"> to a proper <button>,
  eliminating the interactive-element-in-interactive-element violation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tasty007 pushed a commit to tasty007/Archon that referenced this pull request Apr 10, 2026
…up (coleam00#1027)

* Fix: detect squash-merged and PR-merged branches in isolation cleanup (coleam00#1026)

`isolation cleanup --merged` only used `git branch --merged`, which misses
squash-merged branches because the resulting commit has a different SHA.
Bulk cleanup of task worktrees required a manual `gh pr list` per branch.

Changes:
- Add `isPatchEquivalent()` to `@archon/git` using `git cherry` to detect
  squash-merged branches
- Add `getPrState()` to `@archon/isolation` for `gh`-based PR state lookup
  with per-invocation caching; soft-fails on missing gh / non-GitHub remotes
- `cleanupMergedWorktrees()` now unions three signals (ancestry, patch
  equivalence, PR state); skips with a clear reason when PR is OPEN
- Add `--include-closed` flag to `archon isolation cleanup --merged` to
  also remove worktrees whose PRs were closed without merging
- Tests for all new code paths

Fixes coleam00#1026

* fix: address review findings for squash-merge cleanup PR

- branch.ts: add 'bad revision' to isPatchEquivalent expected errors
  so manually-deleted branches return false instead of throwing
- pr-state.ts: add repoPath context to warn/debug log calls;
  capture ghStdout before try block to include in warn log for
  parse failures
- pr-state.test.ts: remove redundant beforeEach reset (setupGhResponse
  already resets); add tests for non-ENOENT gh error and malformed JSON
- cleanup-service.test.ts: add test for isPatchEquivalent unexpected
  throw → skipped with 'merge check failed' reason
- isolation-operations.test.ts: add test for includeClosed: true
  forwarding through cleanupMergedEnvironments
- docs: add --include-closed to all five affected docs (CLAUDE.md,
  reference/cli.md, book/isolation.md, book/quick-reference.md,
  getting-started/overview.md)
- cli-internals.md: add isolation cleanup --merged flow diagram

* simplify: remove redundant assignments and verbose filter in new code
coleam00 and others added 2 commits April 10, 2026 15:48
Three fixes for message duplication during live workflow execution:

1. dag-executor: Add missing `tool_call_formatted` category to loop iteration
   tool messages. Without this, the web adapter sent tool text as both a regular
   SSE text event AND a structured tool_call event, causing each tool to appear
   twice (raw text + rendered card). Regular DAG nodes already had this metadata.

2. WorkflowLogs: Add text content dedup in SSE/DB merge. During live execution,
   the same text (e.g. "Starting workflow...") can appear in both DB (REST fetch)
   and SSE (event buffer replay). Collects DB text into a Set and skips matching
   SSE text messages.

3. orchestrator-agent: Suppress remainingMessage re-send in stream mode. The
   routing AI streams text chunks before /invoke-workflow is detected, then
   retracts them. Without suppression, remainingMessage re-sends the same text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WorkflowLogs' onText handler was blindly concatenating all SSE text into
a single streaming message, unlike ChatInterface which splits on workflow
status text (🚀/✅). This caused the "Starting workflow" text to merge
with subsequent text into one giant message, breaking text dedup against
DB messages (which are stored as separate segments). The SSE message
content never matched any single DB message exactly, so both appeared.

Add the same workflow status boundary detection from ChatInterface:
close the current streaming message and start a new one when a workflow
status message arrives or when regular text follows a status message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/components/workflows/DagNodeProgress.tsx`:
- Around line 26-34: The row div in DagNodeProgress.tsx is not
keyboard-accessible; make it focusable and operable via keyboard by either
replacing the div with a semantic button or adding tabIndex={0} and an onKeyDown
handler that calls onNodeClick(node.nodeId) for Enter and Space, keep role="row"
or change to role="button" and update aria-pressed/aria-current as appropriate
when isActive; ensure you reference the existing onNodeClick, node.nodeId and
isActive symbols when implementing the fix.
- Around line 12-20: Create a new interface named DagNodeItemProps that
describes the props for DagNodeItem (including node: DagNodeState, isActive:
boolean, onNodeClick: (nodeId: string) => void), replace the inline prop type in
the DagNodeItem function signature with this DagNodeItemProps interface, and
update any references/usages to use the named interface to match the existing
DagNodeProgressProps pattern and improve reusability.

In `@packages/web/src/components/workflows/WorkflowLogs.tsx`:
- Around line 391-415: The current prefix-based dedup (using
[...dbTextContents].some(dc => dc.startsWith(m.content))) is too broad and can
hide unrelated SSE text; change it so prefix suppression only runs for actively
streaming/partial SSE messages (e.g., check a flag like m.isStreaming or
m.isPartial) and compare the SSE content only against the latest DB assistant
message instead of every DB message (derive the lastAssistantContent from
filteredDbMessages). Update the condition in the sseMessages loop (where
dbTextContents and dedupedSse are used) to: skip prefix-check unless m is
streaming/partial, and when applied, only test startsWith against
lastAssistantContent.
🪄 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

Run ID: 177a5b16-35d9-49e8-9d5e-c38313d54dd5

📥 Commits

Reviewing files that changed from the base of the PR and between a248438 and 4e56c86.

📒 Files selected for processing (6)
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/web/src/components/workflows/DagNodeProgress.tsx
  • packages/web/src/components/workflows/WorkflowLogs.tsx
  • packages/web/src/hooks/useDashboardSSE.ts
  • packages/web/src/stores/workflow-store.test.ts
  • packages/workflows/src/dag-executor.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/stores/workflow-store.test.ts

Comment on lines +12 to +20
function DagNodeItem({
node,
isActive,
onNodeClick,
}: {
node: DagNodeState;
isActive: boolean;
onNodeClick: (nodeId: string) => void;
}): React.ReactElement {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether DagNodeItem currently uses inline object typing for props.
# Expected: one match on the inline "} : {" props annotation before refactor.
rg -n -C2 --type=tsx 'function DagNodeItem|^\s*}:\s*\{' packages/web/src/components/workflows/DagNodeProgress.tsx

Repository: coleam00/Archon

Length of output: 86


🏁 Script executed:

# Corrected search - use -g glob pattern instead of --type=tsx
rg -n -C3 'function DagNodeItem' packages/web/src/components/workflows/DagNodeProgress.tsx

Repository: coleam00/Archon

Length of output: 180


🏁 Script executed:

# Also read the file to see current state
wc -l packages/web/src/components/workflows/DagNodeProgress.tsx

Repository: coleam00/Archon

Length of output: 120


🏁 Script executed:

# Check for any existing props interfaces in this file
rg -n 'interface.*Props|type.*Props' packages/web/src/components/workflows/DagNodeProgress.tsx

Repository: coleam00/Archon

Length of output: 93


🏁 Script executed:

# Read lines 6-40 to see DagNodeProgressProps interface and DagNodeItem function
sed -n '6,40p' packages/web/src/components/workflows/DagNodeProgress.tsx

Repository: coleam00/Archon

Length of output: 1016


Extract DagNodeItem props into a dedicated DagNodeItemProps interface.

Component props should use interfaces per the TypeScript guidelines for major abstractions. Currently, props are defined inline, which reduces reusability and inconsistency with the existing DagNodeProgressProps pattern used elsewhere in this file.

Suggested refactor
+interface DagNodeItemProps {
+  node: DagNodeState;
+  isActive: boolean;
+  onNodeClick: (nodeId: string) => void;
+}
+
 function DagNodeItem({
   node,
   isActive,
   onNodeClick,
-}: {
-  node: DagNodeState;
-  isActive: boolean;
-  onNodeClick: (nodeId: string) => void;
-}): React.ReactElement {
+}: DagNodeItemProps): React.ReactElement {
📝 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
function DagNodeItem({
node,
isActive,
onNodeClick,
}: {
node: DagNodeState;
isActive: boolean;
onNodeClick: (nodeId: string) => void;
}): React.ReactElement {
interface DagNodeItemProps {
node: DagNodeState;
isActive: boolean;
onNodeClick: (nodeId: string) => void;
}
function DagNodeItem({
node,
isActive,
onNodeClick,
}: DagNodeItemProps): React.ReactElement {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/components/workflows/DagNodeProgress.tsx` around lines 12 -
20, Create a new interface named DagNodeItemProps that describes the props for
DagNodeItem (including node: DagNodeState, isActive: boolean, onNodeClick:
(nodeId: string) => void), replace the inline prop type in the DagNodeItem
function signature with this DagNodeItemProps interface, and update any
references/usages to use the named interface to match the existing
DagNodeProgressProps pattern and improve reusability.

Comment on lines +26 to +34
<div
className={`w-full text-left px-2 py-1.5 rounded transition-colors cursor-pointer ${
isActive ? 'bg-accent/10 border-l-2 border-accent' : 'hover:bg-surface-hover'
}`}
onClick={(): void => {
onNodeClick(node.nodeId);
}}
role="row"
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Row selection is not keyboard-accessible.

The clickable row is a non-focusable <div> with mouse-only activation. Keyboard users can’t select a node from this control.

♿ Minimal accessibility fix
       <div
         className={`w-full text-left px-2 py-1.5 rounded transition-colors cursor-pointer ${
           isActive ? 'bg-accent/10 border-l-2 border-accent' : 'hover:bg-surface-hover'
         }`}
         onClick={(): void => {
           onNodeClick(node.nodeId);
         }}
-        role="row"
+        onKeyDown={(e): void => {
+          if (e.key === 'Enter' || e.key === ' ') {
+            e.preventDefault();
+            onNodeClick(node.nodeId);
+          }
+        }}
+        role="button"
+        tabIndex={0}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/components/workflows/DagNodeProgress.tsx` around lines 26 -
34, The row div in DagNodeProgress.tsx is not keyboard-accessible; make it
focusable and operable via keyboard by either replacing the div with a semantic
button or adding tabIndex={0} and an onKeyDown handler that calls
onNodeClick(node.nodeId) for Enter and Space, keep role="row" or change to
role="button" and update aria-pressed/aria-current as appropriate when isActive;
ensure you reference the existing onNodeClick, node.nodeId and isActive symbols
when implementing the fix.

Comment on lines +391 to +415
// Collect DB text content for dedup against SSE text messages.
// During live execution, the same text (e.g., "🚀 Starting workflow...") can appear
// in both DB (from REST fetch on mount) and SSE (from event buffer replay).
// Without dedup, the text shows up twice in the message list.
const dbTextContents = new Set<string>();
for (const dm of filteredDbMessages) {
if (dm.role === 'assistant' && dm.content) {
dbTextContents.add(dm.content);
}
}

// Strip SSE tool calls that already appear in DB messages (completed).
// Also strip SSE text messages that are already in DB (prevents duplicate text).
const dedupedSse: ChatMessage[] = [];
for (const m of sseMessages) {
if (!m.toolCalls?.length) {
// Skip SSE text-only messages whose content already exists in DB.
if (m.content && dbTextContents.has(m.content)) {
continue;
}
// Also skip if DB has a message that starts with the SSE content
// (SSE text was flushed to DB before SSE finished accumulating).
if (m.content && [...dbTextContents].some(dc => dc.startsWith(m.content))) {
continue;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prefix dedup is too broad and can hide valid live SSE text.

At Line 413, prefix matching against any DB assistant content can suppress unrelated SSE messages (especially short/common prefixes). Limit prefix-based suppression to actively streaming SSE text and preferably only against the latest DB assistant message.

Suggested adjustment
-    const dbTextContents = new Set<string>();
-    for (const dm of filteredDbMessages) {
-      if (dm.role === 'assistant' && dm.content) {
-        dbTextContents.add(dm.content);
-      }
-    }
+    const dbAssistantContents = filteredDbMessages
+      .filter((dm): dm is ChatMessage => dm.role === 'assistant' && !!dm.content)
+      .map(dm => dm.content);
+    const dbTextContents = new Set<string>(dbAssistantContents);
+    const latestDbAssistantContent =
+      dbAssistantContents.length > 0 ? dbAssistantContents[dbAssistantContents.length - 1] : '';

@@
-        if (m.content && [...dbTextContents].some(dc => dc.startsWith(m.content))) {
+        if (m.isStreaming && m.content && latestDbAssistantContent.startsWith(m.content)) {
           continue;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/components/workflows/WorkflowLogs.tsx` around lines 391 -
415, The current prefix-based dedup (using [...dbTextContents].some(dc =>
dc.startsWith(m.content))) is too broad and can hide unrelated SSE text; change
it so prefix suppression only runs for actively streaming/partial SSE messages
(e.g., check a flag like m.isStreaming or m.isPartial) and compare the SSE
content only against the latest DB assistant message instead of every DB message
(derive the lastAssistantContent from filteredDbMessages). Update the condition
in the sseMessages loop (where dbTextContents and dedupedSse are used) to: skip
prefix-check unless m is streaming/partial, and when applied, only test
startsWith against lastAssistantContent.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/web/src/components/workflows/WorkflowLogs.tsx (1)

391-415: ⚠️ Potential issue | 🟡 Minor

Narrow the prefix-based SSE text dedup.

The startsWith suppression still checks against every persisted assistant message, so an older DB message can hide unrelated live SSE text that merely shares a short prefix. Restrict this prefix check to actively streaming SSE text and compare only against the latest DB assistant message.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/components/workflows/WorkflowLogs.tsx` around lines 391 -
415, The prefix-based suppression is too broad: change the startsWith check so
it only compares the SSE message content (m.content) against the most recent
persisted assistant message, not every DB message. Compute the latest assistant
content from filteredDbMessages (find the last dm where dm.role === 'assistant'
&& dm.content) and then, only when the SSE message is actively streaming (check
the streaming flag on the SSE message, e.g., m.isStreaming or equivalent), skip
the SSE message if latestAssistantContent.startsWith(m.content); remove the
[...dbTextContents].some(...) scan and use latestAssistantContent instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/web/src/components/workflows/WorkflowLogs.tsx`:
- Around line 391-415: The prefix-based suppression is too broad: change the
startsWith check so it only compares the SSE message content (m.content) against
the most recent persisted assistant message, not every DB message. Compute the
latest assistant content from filteredDbMessages (find the last dm where dm.role
=== 'assistant' && dm.content) and then, only when the SSE message is actively
streaming (check the streaming flag on the SSE message, e.g., m.isStreaming or
equivalent), skip the SSE message if
latestAssistantContent.startsWith(m.content); remove the
[...dbTextContents].some(...) scan and use latestAssistantContent instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2ea9c42-56a4-4064-9d6d-7523a5a0f67d

📥 Commits

Reviewing files that changed from the base of the PR and between 4e56c86 and 1eddf3e.

📒 Files selected for processing (1)
  • packages/web/src/components/workflows/WorkflowLogs.tsx

The suppression broke the "sends remaining message before dispatching
workflow" test — when the AI response contains both text and a command
in a single chunk, the text was never streamed, so suppressing
remainingMessage loses it entirely. The actual duplicate was in the
WorkflowLogs execution view, not the routing AI path, and is already
fixed by the onText message splitting and text content dedup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coleam00 coleam00 merged commit 536584d into dev Apr 10, 2026
3 of 4 checks passed
Tyone88 pushed a commit to Tyone88/Archon that referenced this pull request Apr 16, 2026
…up (coleam00#1027)

* Fix: detect squash-merged and PR-merged branches in isolation cleanup (coleam00#1026)

`isolation cleanup --merged` only used `git branch --merged`, which misses
squash-merged branches because the resulting commit has a different SHA.
Bulk cleanup of task worktrees required a manual `gh pr list` per branch.

Changes:
- Add `isPatchEquivalent()` to `@archon/git` using `git cherry` to detect
  squash-merged branches
- Add `getPrState()` to `@archon/isolation` for `gh`-based PR state lookup
  with per-invocation caching; soft-fails on missing gh / non-GitHub remotes
- `cleanupMergedWorktrees()` now unions three signals (ancestry, patch
  equivalence, PR state); skips with a clear reason when PR is OPEN
- Add `--include-closed` flag to `archon isolation cleanup --merged` to
  also remove worktrees whose PRs were closed without merging
- Tests for all new code paths

Fixes coleam00#1026

* fix: address review findings for squash-merge cleanup PR

- branch.ts: add 'bad revision' to isPatchEquivalent expected errors
  so manually-deleted branches return false instead of throwing
- pr-state.ts: add repoPath context to warn/debug log calls;
  capture ghStdout before try block to include in warn log for
  parse failures
- pr-state.test.ts: remove redundant beforeEach reset (setupGhResponse
  already resets); add tests for non-ENOENT gh error and malformed JSON
- cleanup-service.test.ts: add test for isPatchEquivalent unexpected
  throw → skipped with 'merge check failed' reason
- isolation-operations.test.ts: add test for includeClosed: true
  forwarding through cleanupMergedEnvironments
- docs: add --include-closed to all five affected docs (CLAUDE.md,
  reference/cli.md, book/isolation.md, book/quick-reference.md,
  getting-started/overview.md)
- cli-internals.md: add isolation cleanup --merged flow diagram

* simplify: remove redundant assignments and verbose filter in new code
Tyone88 pushed a commit to Tyone88/Archon that referenced this pull request Apr 16, 2026
…ed button, guard, tests

- Preserve accumulated iteration state in handleDagNode by spreading existing node before overwriting with event fields (HIGH: iteration badge/list vanished on node completion)
- Replace nested <button> with <span role="button"> + onKeyDown in DagNodeProgress to fix HTML spec violation and broken stopPropagation (HIGH: accessibility + mis-navigation)
- Fix falsy guard `if (!iteration)` → `if (iteration === undefined)` in WorkflowExecution REST enrichment (MEDIUM: would silently drop iteration 0 in future)
- Fix dead file reference in workflow-bridge comments: `useWorkflowStatus.ts` → `workflow-store.ts handleLoopIteration` (MEDIUM: misleading comment)
- Add 7 unit tests for handleLoopIteration in workflow-store.test.ts covering all branches: no-nodeId no-op, ghost-node no-op, first append, upsert, total:0 preservation, accumulation, and iteration state survival across dag_node completion (MEDIUM: zero coverage for core PR logic)
- Clarify two LOW comment gaps in WorkflowExecution.tsx and workflow-store.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tyone88 pushed a commit to Tyone88/Archon that referenced this pull request Apr 16, 2026
…1014

feat(web): loop node iteration visibility in workflow execution view
joaobmonteiro pushed a commit to joaobmonteiro/Archon that referenced this pull request Apr 26, 2026
…up (coleam00#1027)

* Fix: detect squash-merged and PR-merged branches in isolation cleanup (coleam00#1026)

`isolation cleanup --merged` only used `git branch --merged`, which misses
squash-merged branches because the resulting commit has a different SHA.
Bulk cleanup of task worktrees required a manual `gh pr list` per branch.

Changes:
- Add `isPatchEquivalent()` to `@archon/git` using `git cherry` to detect
  squash-merged branches
- Add `getPrState()` to `@archon/isolation` for `gh`-based PR state lookup
  with per-invocation caching; soft-fails on missing gh / non-GitHub remotes
- `cleanupMergedWorktrees()` now unions three signals (ancestry, patch
  equivalence, PR state); skips with a clear reason when PR is OPEN
- Add `--include-closed` flag to `archon isolation cleanup --merged` to
  also remove worktrees whose PRs were closed without merging
- Tests for all new code paths

Fixes coleam00#1026

* fix: address review findings for squash-merge cleanup PR

- branch.ts: add 'bad revision' to isPatchEquivalent expected errors
  so manually-deleted branches return false instead of throwing
- pr-state.ts: add repoPath context to warn/debug log calls;
  capture ghStdout before try block to include in warn log for
  parse failures
- pr-state.test.ts: remove redundant beforeEach reset (setupGhResponse
  already resets); add tests for non-ENOENT gh error and malformed JSON
- cleanup-service.test.ts: add test for isPatchEquivalent unexpected
  throw → skipped with 'merge check failed' reason
- isolation-operations.test.ts: add test for includeClosed: true
  forwarding through cleanupMergedEnvironments
- docs: add --include-closed to all five affected docs (CLAUDE.md,
  reference/cli.md, book/isolation.md, book/quick-reference.md,
  getting-started/overview.md)
- cli-internals.md: add isolation cleanup --merged flow diagram

* simplify: remove redundant assignments and verbose filter in new code
joaobmonteiro pushed a commit to joaobmonteiro/Archon that referenced this pull request Apr 26, 2026
…ed button, guard, tests

- Preserve accumulated iteration state in handleDagNode by spreading existing node before overwriting with event fields (HIGH: iteration badge/list vanished on node completion)
- Replace nested <button> with <span role="button"> + onKeyDown in DagNodeProgress to fix HTML spec violation and broken stopPropagation (HIGH: accessibility + mis-navigation)
- Fix falsy guard `if (!iteration)` → `if (iteration === undefined)` in WorkflowExecution REST enrichment (MEDIUM: would silently drop iteration 0 in future)
- Fix dead file reference in workflow-bridge comments: `useWorkflowStatus.ts` → `workflow-store.ts handleLoopIteration` (MEDIUM: misleading comment)
- Add 7 unit tests for handleLoopIteration in workflow-store.test.ts covering all branches: no-nodeId no-op, ghost-node no-op, first append, upsert, total:0 preservation, accumulation, and iteration state survival across dag_node completion (MEDIUM: zero coverage for core PR logic)
- Clarify two LOW comment gaps in WorkflowExecution.tsx and workflow-store.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
joaobmonteiro pushed a commit to joaobmonteiro/Archon that referenced this pull request Apr 26, 2026
…1014

feat(web): loop node iteration visibility in workflow execution view
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.

feat(web): loop node iteration visibility in workflow execution view

1 participant