Skip to content

fix(cue): empty-graph CTA + tighten regression coverage#924

Merged
reachrazamair merged 2 commits intorcfrom
cue-polish
Apr 29, 2026
Merged

fix(cue): empty-graph CTA + tighten regression coverage#924
reachrazamair merged 2 commits intorcfrom
cue-polish

Conversation

@reachrazamair
Copy link
Copy Markdown
Contributor

@reachrazamair reachrazamair commented Apr 29, 2026

Summary

Fixes the bug where the Cue pipeline editor showed a spinner forever instead of the "Create your first pipeline" CTA when no Cue subscriptions were registered. Bundles regression coverage for five recent fixes that shipped without tests, plus a round-trip harness for the pipeline ↔ YAML serialization path.

The bug fix

useCueGraphData returns [] when no agent has Cue subscriptions and flips graphInitialLoading to false. But usePipelineLayout's restore effect early-returns on empty graphSessions and never marks pipelinesLoaded=true. The editor's graphLoading || !pipelinesLoaded gate then evaluated to false || true === true, so the spinner never went away.

One-line fix at the parent boundary: gate !pipelinesLoaded behind graphSessions.length > 0 so an empty fetch result correctly drops to the CTA.

- isLoading={graphLoading || !pipelinesLoaded}
+ isLoading={graphLoading || (graphSessions.length > 0 && !pipelinesLoaded)}

The usePipelineLayout hook's semantics and existing tests stay untouched — the integration logic lives where both signals are available.

Why the broader test additions

Recent Cue branch history is ~53% bugfix commits. The pattern: most bugs sit at integration boundaries (data round-trips, multi-pipeline state, race conditions) — places unit tests don't cover. This PR closes the highest-value gaps:

Regression tests for five recent fixes that shipped without coverage:

Fix Bug shape New tests
42ac8333e Tab-switch must clear pendingPipelineId; toast title leaked raw ISO 3
96a87a19c Agent panel header missing instance suffix when sessionId shared 4
f94108e7b usePipelineLayout race when graphSessions change mid-fetch 2
30ab9a5c4 Fan-in tracker dropped chainDepth, breaking depth guard on cycles 3
d85290c51 calculateNextScheduledTime returned null for same-day-next-week 2

Round-trip harness (pipelineRoundTrip.shapes.test.ts):

Mirrors the runtime's pipelinesToYaml → yaml.load → subscriptionsToPipelines cycle, including the normalizer's prompt-file inlining (handles both prompt_file and fan_out_prompt_files). 10 tests covering trigger fan-out, multi-agent chains, every trigger event config (heartbeat / scheduled / file.changed / github.pull_request), and double-round-trip idempotence.

While building it I uncovered two design facts now documented inline so future readers don't waste time on the same dead-ends:

  • Edge mode (autorun / debate) is YAML-comment-only by design (getEdgeModeComment) and recovered from the layout sidecar, not the YAML body
  • Chain prompts canonically live on agent.inputPrompt, not on the chain edge — pipelineToYaml only reads edge.prompt for trigger→agent edges

Honest scope note

These are bounded regression guards anchored to specific fixes — they catch reverts of the corresponding code, not novel bug paths. The round-trip harness is the most generalizable: any future field-loss in the YAML serialization path will fail it.

The 5-pick list deliberately skipped 89833d325 (scheduledFiredKeys session scoping) because the registry refactor makes the bug structurally impossible — re-introducing it would require an API change that existing tests already block.

Stats

  • 2 commits: the fix (with its 6-test regression file) is committable on its own; the broader test additions are a separate commit so they can be reverted independently
  • 30 new tests across 8 files
  • Cue suite: 2,164 tests / 116 files passing, typecheck clean

Test plan

  • Open Cue modal on a project with no Cue subscriptions → "Create your first pipeline" CTA renders (no spinner)
  • Open Cue modal on a project with existing pipelines → editor renders normally, no flash of CTA
  • During the initial graph fetch the spinner still shows (confirm graphLoading=true path unchanged)
  • npm run test -- --run src/__tests__/main/cue src/__tests__/renderer/components/CuePipelineEditor src/__tests__/renderer/hooks/cue src/__tests__/renderer/components/CueModal.test.tsx src/__tests__/renderer/hooks/useCue.test.ts src/__tests__/shared/cue passes
  • npx tsc --noEmit clean

Summary by CodeRabbit

  • Bug Fixes
    • Fixed the pipeline editor's loading indicator to clear properly when viewing empty graphs, preventing an indefinite loading state when a pipeline contains no sessions.

Trace: useCueGraphData fetches and returns []; usePipelineLayout's
restore effect early-returns on empty graphSessions and never marks
pipelinesLoaded=true. The editor's `graphLoading || !pipelinesLoaded`
gate then evaluated to `false || true === true`, leaving the spinner
up forever instead of falling through to "Create your first pipeline".

Fix gates `!pipelinesLoaded` behind `graphSessions.length > 0` so an
empty fetch result correctly drops to the CTA. Surgical one-line change
at the parent boundary; usePipelineLayout's hook semantics and tests
stay untouched.

Regression test: CuePipelineEditor.loadingGate.test.tsx covers the four
combinations of graphLoading × pipelinesLoaded × graphSessions empty/non.
Closes coverage gaps for five recent fixes that shipped without
regression tests, and adds a round-trip shapes harness to catch
future field-loss bugs in the pipeline ↔ YAML serialization path.

Regression tests for previously-uncovered fixes:
- 42ac833: tab switch must clear pendingPipelineId; toast title
  drops the raw ISO leak in favor of locale time + ms
- 96a87a1: NodeConfigPanel header mirrors the canvas's "(N)"
  instance suffix, isolated per pipeline
- f94108e: usePipelineLayout's request-id guard wins both
  stale-resolves-first and fresh-resolves-first orderings
- 30ab9a5: fan-in tracker forwards max chainDepth across
  completions in both all-complete and timeout-continue paths
- d85290c: calculateNextScheduledTime resolves same-day-next-week
  when today's slot has already passed

Round-trip harness (pipelineRoundTrip.shapes.test.ts):
- Trigger fan-out preserves all targets + per-target prompts
  (fan_out_prompt_files distribution)
- Multi-agent linear chain preserves topology + agent inputPrompts
- Trigger event configs (heartbeat, scheduled, file.changed,
  github.pull_request) round-trip exactly
- Double round-trip is structurally idempotent

Comments call out two design facts surfaced while building this:
edge mode (autorun/debate) is YAML-comment-only and is recovered
from pipelineLayout.json sidecar, not the YAML body; chain prompts
canonically live on agent.inputPrompt, not edge.prompt — so future
readers don't waste time on the same dead-ends.

30 tests added across 8 files. Cue suite now at 2,164 tests / 116
files passing.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

This PR adds comprehensive regression test coverage across the Cue engine, pipeline editor, and related components, including tests for scheduling behavior, fan-in depth propagation, tab navigation, loading state transitions, agent header rendering, pipeline serialization round-trips, and race conditions in layout restoration. It also includes a minor fix to the PipelineCanvas loading condition.

Changes

Cohort / File(s) Summary
Cue Engine & Tracking Tests
src/__tests__/main/cue/cue-engine.test.ts, src/__tests__/main/cue/cue-fan-in-tracker.test.ts
Added tests verifying calculateNextScheduledTime recurrence behavior across week boundaries and validating chainDepth propagation through fan-in dispatch across all-complete, timeout-continue, and default scenarios.
CueModal & Navigation Tests
src/__tests__/renderer/components/CueModal.test.tsx
Enhanced test suite to track CuePipelineEditor mock props (initialPipelineId) and added regression tests verifying tab-navigation behavior around the "View in Pipeline" token, ensuring navigation away clears the token while staying on the pipeline tab preserves it.
CuePipelineEditor Loading & Configuration Tests
src/__tests__/renderer/components/CuePipelineEditor/CuePipelineEditor.loadingGate.test.tsx, src/__tests__/renderer/components/CuePipelineEditor/panels/NodeConfigPanel.test.tsx
Added comprehensive test suites for loading state transitions (isLoading prop behavior with graphLoading and pipelinesLoaded) and agent header rendering with session-id suffix logic across single/multiple pipeline contexts.
Pipeline Serialization & Layout Tests
src/__tests__/renderer/components/CuePipelineEditor/utils/pipelineRoundTrip.shapes.test.ts, src/__tests__/renderer/hooks/cue/usePipelineLayout.test.ts
Introduced round-trip integration tests for pipeline YAML serialization (trigger fan-out, multi-agent chains, event config preservation) and regression tests for race conditions in loadPipelineLayout restoration, validating that fresh restore results override stale invocations.
Hook Tests
src/__tests__/renderer/hooks/useCue.test.ts
Added unit test validating toast-title formatting for queueOverflow notifications, ensuring ISO date formatting is excluded and millisecond duration is included.
Implementation Change
src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx
Refined PipelineCanvas loading condition so pipelinesLoaded only affects isLoading when graphSessions has entries; otherwise isLoading is driven solely by graphLoading.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

ready to merge

Poem

🐰 Through scheduling hops and loading gates,
We chase the fan-in that propagates,
Round-trip serialization holds its ground,
While race conditions can't be found,
The pipeline dances in perfect sync—tests have won!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% 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 accurately summarizes the main changes: fixing the empty-graph CTA bug and adding regression test coverage. It is concise, specific, and clearly reflects the primary objectives.
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.

✏️ 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 cue-polish

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

The one-line production change corrects a loading gate in CuePipelineEditor: the old expression graphLoading || !pipelinesLoaded kept the spinner indefinitely when graphSessions was empty (because usePipelineLayout never sets pipelinesLoaded=true on an empty session list). The fix graphLoading || (graphSessions.length > 0 && !pipelinesLoaded) correctly short-circuits to the "Create your first pipeline" CTA. The remaining changes are regression tests for five previously uncovered bug fixes and a new round-trip serialization harness.

Confidence Score: 4/5

Safe to merge — production change is a correct one-liner; only a minor test harness fidelity gap was found.

The production fix is logically sound and verified against all four gate states in the new tests. The only finding is a P2 in the round-trip test helper that leaves fan_out_prompt_files in the subscription object alongside the resolved fan_out_prompts, which is benign today but could mask failures if a future fallback path is added.

src/tests/renderer/components/CuePipelineEditor/utils/pipelineRoundTrip.shapes.test.ts — the roundTrip helper should also strip fan_out_prompt_files from the subscription object.

Important Files Changed

Filename Overview
src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx One-line fix: gates !pipelinesLoaded behind graphSessions.length > 0 so an empty graph fetch correctly drops to the CTA instead of spinning forever.
src/tests/renderer/components/CuePipelineEditor/CuePipelineEditor.loadingGate.test.tsx New regression test file with 6 tests covering the four-way truth table for the isLoading gate; correctly captures PipelineCanvas.isLoading prop via a mock boundary.
src/tests/renderer/components/CuePipelineEditor/utils/pipelineRoundTrip.shapes.test.ts New round-trip harness for 10 pipeline serialization tests; roundTrip helper leaves fan_out_prompt_files in subscription objects alongside fan_out_prompts, creating a test fidelity gap if a future fallback path is added.
src/tests/renderer/hooks/cue/usePipelineLayout.test.ts Adds two well-structured tests for the request-ID race guard (stale in-flight load must not overwrite a newer one), correctly using deferred promises to control resolution order.
src/tests/main/cue/cue-fan-in-tracker.test.ts Adds 3 regression tests for chainDepth propagation through fan-in dispatch, including the NaN-from-undefined defensive case.
src/tests/main/cue/cue-engine.test.ts Adds 2 regression tests for calculateNextScheduledTime returning null when a single-day schedule's slot has already passed (the <= 7 bound fix).
src/tests/renderer/components/CueModal.test.tsx Adds 3 regression tests for tab-switch clearing pendingPipelineId; uses vi.hoisted correctly to capture editor props before the mock factory runs.
src/tests/renderer/components/CuePipelineEditor/panels/NodeConfigPanel.test.tsx Adds 4 regression tests for agent instance suffix in the config panel header when multiple nodes share a sessionId, including cross-pipeline isolation and orphan-node defensive case.
src/tests/renderer/hooks/useCue.test.ts Adds 1 regression test asserting that queueOverflow toast titles use localized time + ms suffix rather than leaking raw ISO-8601 strings.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["useCueGraphData fetch completes"] --> B{graphSessions.length > 0?}
    B -- "No (empty graph)" --> C["isLoading = graphLoading || false"]
    C --> D{graphLoading?}
    D -- "true" --> E["Spinner (fetch in progress)"]
    D -- "false" --> F["CTA: Create your first pipeline"]
    B -- "Yes (has sessions)" --> G["isLoading = graphLoading || !pipelinesLoaded"]
    G --> H{graphLoading?}
    H -- "true" --> E
    H -- "false" --> I{pipelinesLoaded?}
    I -- "false" --> E2["Spinner (layout restoring)"]
    I -- "true" --> J["Pipeline Editor renders"]
Loading

Comments Outside Diff (1)

  1. src/__tests__/renderer/components/CuePipelineEditor/utils/pipelineRoundTrip.shapes.test.ts, line 855-862 (link)

    P2 fan_out_prompt_files not stripped from round-trip subscriptions

    The destructuring strips prompt_file and output_prompt_file but leaves fan_out_prompt_files in rest, so the CueSubscription objects handed to subscriptionsToPipelines carry both fan_out_prompt_files (raw file-path strings) and fan_out_prompts (resolved prompt text) simultaneously. Today this is harmless because the runtime only reads fan_out_prompts, but the CueSubscription JSDoc explicitly notes that fan_out_prompt_files is the "preferred new write" format and that normalizers are expected to resolve it — meaning a future fallback path in subscriptionsToPipelines could silently pick up file paths instead of prompt text and make these round-trip assertions pass with wrong data.

Reviews (1): Last reviewed commit: "test(cue): regression coverage for recen..." | Re-trigger Greptile

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

🧹 Nitpick comments (1)
src/__tests__/main/cue/cue-engine.test.ts (1)

1856-1860: Test name/comment is slightly misleading vs asserted behavior.

Line 1856 says “same day next week,” but this case asserts Friday of the same week (Line 1865-1866). Consider renaming for clarity.

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

In `@src/__tests__/main/cue/cue-engine.test.ts` around lines 1856 - 1860, The test
name and leading comment for the spec using the case "resolves to same day next
week with multi-day filter when today is the only matching day past its slot"
are misleading because the assertions check that the schedule picks Friday of
the same week (not next Wednesday); update the test title string and the comment
inside the test to accurately describe that when Wednesday's slot is past and
Friday is the next matching day, the engine resolves to the same week's Friday
(preserving the "picks-earliest" semantics). Locate the spec by its name in the
test suite (the string passed to it(...) around the block and the inline comment
referencing "Wednesday 2026-03-11 at 23:59 — schedule fires Wed/Fri at 09:00")
and change both the test name and comment to reflect "resolves to same-week
Friday with multi-day filter when today is the only matching day past its slot"
or equivalent clearer wording.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/renderer/components/CueModal.test.tsx`:
- Around line 447-448: The assertion checking
capturedEditorProps.initialPipelineId is vacuous unless the editor actually
mounted; update the test to either remove that line or first assert the editor
mounted by checking the renderCount (e.g., ensure renderCount for
CuePipelineEditor is > 0) before reading capturedEditorProps, so you only
inspect initialPipelineId after confirming CuePipelineEditor rendered.

---

Nitpick comments:
In `@src/__tests__/main/cue/cue-engine.test.ts`:
- Around line 1856-1860: The test name and leading comment for the spec using
the case "resolves to same day next week with multi-day filter when today is the
only matching day past its slot" are misleading because the assertions check
that the schedule picks Friday of the same week (not next Wednesday); update the
test title string and the comment inside the test to accurately describe that
when Wednesday's slot is past and Friday is the next matching day, the engine
resolves to the same week's Friday (preserving the "picks-earliest" semantics).
Locate the spec by its name in the test suite (the string passed to it(...)
around the block and the inline comment referencing "Wednesday 2026-03-11 at
23:59 — schedule fires Wed/Fri at 09:00") and change both the test name and
comment to reflect "resolves to same-week Friday with multi-day filter when
today is the only matching day past its slot" or equivalent clearer wording.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 593b47b3-9f04-47da-abaf-fb30d20c64ec

📥 Commits

Reviewing files that changed from the base of the PR and between 71ced16 and 239c9a0.

📒 Files selected for processing (9)
  • src/__tests__/main/cue/cue-engine.test.ts
  • src/__tests__/main/cue/cue-fan-in-tracker.test.ts
  • src/__tests__/renderer/components/CueModal.test.tsx
  • src/__tests__/renderer/components/CuePipelineEditor/CuePipelineEditor.loadingGate.test.tsx
  • src/__tests__/renderer/components/CuePipelineEditor/panels/NodeConfigPanel.test.tsx
  • src/__tests__/renderer/components/CuePipelineEditor/utils/pipelineRoundTrip.shapes.test.ts
  • src/__tests__/renderer/hooks/cue/usePipelineLayout.test.ts
  • src/__tests__/renderer/hooks/useCue.test.ts
  • src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx

Comment on lines +447 to +448
// Default tab is 'pipeline' — editor renders with no pending token.
expect(capturedEditorProps.initialPipelineId).toBeUndefined();
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

This initial token check can pass without mounting the editor.

beforeEach already resets capturedEditorProps, and this suite earlier asserts the dashboard is the default view, so expect(capturedEditorProps.initialPipelineId).toBeUndefined() is vacuous unless you first prove CuePipelineEditor rendered. Either drop this check or use the new renderCount capture to assert the editor actually mounted before reading its props.

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

In `@src/__tests__/renderer/components/CueModal.test.tsx` around lines 447 - 448,
The assertion checking capturedEditorProps.initialPipelineId is vacuous unless
the editor actually mounted; update the test to either remove that line or first
assert the editor mounted by checking the renderCount (e.g., ensure renderCount
for CuePipelineEditor is > 0) before reading capturedEditorProps, so you only
inspect initialPipelineId after confirming CuePipelineEditor rendered.

@reachrazamair reachrazamair merged commit d40f4e2 into rc Apr 29, 2026
5 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 30, 2026
6 tasks
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.

1 participant