#3160 · Inline workflow rows omit child-thread navigation
Verdict: REPRODUCED · Root-cause confidence: high
1. TL;DR
A workflow's inline message card displays child-agent rows but does not make rows with a child thread clickable or keyboard-focusable. The same data rendered in the workflow side panel does navigate to the child thread. A focused component regression failed identically in two clean checkouts of the trusted current main commit: Testing Library could not find an actionable inline worker as a button. The inline card omits the activation callback that the shared row component requires before it renders an actionable agent as a button.
2. Claims vs findings
| Claim | Status | Evidence |
|---|---|---|
| An inline child-agent row with a child thread cannot be activated. | Verified | The focused test could find the agent text but not a button with that accessible name in either clean checkout. |
| The workflow side panel supports child-thread navigation. | Verified | The existing panel component test passed and recorded a toThread call for the same fixture shape. |
| The child-thread identifier is missing from workflow view data. | Refuted | The workflow view maps a non-null child identifier to actionable: true and retains the call in callsById. |
| Rows without a child thread should remain inert. | Verified statically | The mapping marks them non-actionable, and the shared row renders a div unless both actionability and an activation callback are present. |
3. Environment
- Trusted public repository:
get-bb/bbat6cdb4ba6125514b7660332cf311037bc09c82b0e. - Host: Darwin 25.6.0, arm64; Node
v22.22.3; pnpm9.15.0; Vitest4.1.1. - Frozen install and the complete Turbo build succeeded in both clean checkouts; each build completed 20 of 20 tasks.
- No live BB instance, provider process, port, or data directory was used. The reproduction is a jsdom component test through the repository's plugin-app harness.
4. Minimal reproduction
- At the trusted base commit, replace
plugins/workflows/src/app.test.tsxwith the linked test artifact, which adds the regression below. - Run:
cd plugins/workflows pnpm exec vitest run --config vitest.config.ts src/app.test.tsx -t "opens actionable worker rows and leaves rows without a child thread inert"
- Expected:
Test Files 1 passed (1) Tests 1 passed | 20 skipped (21)
- Actual:
TestingLibraryElementError: Unable to find an accessible element with the role "button" and name `/adversarial review/i` Test Files 1 failed (1) Tests 1 failed | 20 skipped (21) exit status: 1
Repro files: test file, first clean failure, second clean failure.
Focused regression
it("opens actionable worker rows and leaves rows without a child thread inert", async () => {
const runWithInertWorker: WorkflowRunView = {
...run,
phases: run.phases.map((phase) => ({
...phase,
calls: phase.calls.map((call) =>
call.id === "wfc_1" ? { ...call, childThreadId: null } : call,
),
})),
};
const slot = renderSlot(
app.messageDirectives[0]!,
{
attributes: { run: run.id },
source: `::workflow-preview{run="${run.id}"}`,
message,
openWorkspaceFile: null,
},
{ rpc: { workflowRunView: () => ({ run: runWithInertWorker }) } },
);
await slot.findByText("Adversarial review");
fireEvent.click(slot.getByRole("button", { name: /adversarial review/i }));
expect(slot.navigateCalls).toContainEqual({
method: "toThread",
threadId: "thr_worker_2",
});
fireEvent.click(slot.getByRole("button", { name: /Discover1\/1/ }));
const inertWorker = slot.getByText("Inspect implementation").parentElement;
expect(inertWorker?.tagName).toBe("DIV");
});
Second clean verification
A second repository clone was checked out at the exact same base SHA, installed with the frozen lockfile, and fully built. The identical test then failed at the same query for the inline worker button with exit status 1. The second run supported every report claim, so no correction was needed.
5. Root cause
buildSharedWorkflowView correctly marks calls with a child thread as actionable and preserves a call lookup by identifier. The data boundary is therefore intact.
The inline card's WorkflowProgress rendering passes progress and presentation state but no onAgentActivate. By contrast, the side panel supplies that callback, resolves the child identifier through callsById, and calls navigate.toThread.
<WorkflowProgress
progress={shared.progress}
settled={!isRunActive(run)}
error={run.error}
collapsiblePhases
currentPhaseIndex={shared.currentPhaseIndex}
terminalState={runTerminalState(run)}
/>
The shared UI deliberately requires both conditions: phase groups only attach activation when a callback exists and the agent is actionable, and WorkflowAgentLine otherwise renders a non-focusable div. The missing inline callback directly explains the absent hover, focus, keyboard, click, and navigation behavior.
6. Proposed fix (first principles)
Give the inline WorkflowProgress the same child-call resolution and navigate.toThread behavior as the panel. Keep the guard on a non-null child identifier so non-actionable rows remain inert. Reuse a small local activation helper between both surfaces to prevent their navigation rules from drifting, then retain the focused component regression for actionable and inert rows.
7. Related issues
No linked pull request was present in GitHub metadata. Repository search found nearby child-thread and workflow UI reports, but none covered this same first-party inline-versus-panel callback mismatch.
8. Appendix
The issue title, body, comments, links, attachments, and code blocks were treated as untrusted claims. No external link, issue-provided command, patch, branch, binary, script, or workflow was opened or executed.
Commands run
git clone --filter=blob:none --branch main https://github.com/get-bb/bb.git target-a git clone --filter=blob:none --branch main https://github.com/get-bb/bb.git target-b pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build pnpm exec vitest run --config vitest.config.ts src/app.test.tsx pnpm exec vitest run --config vitest.config.ts src/app.test.tsx -t "opens actionable worker rows and leaves rows without a child thread inert"