Skip to content

feat(cue): app.startup trigger, pipeline numbering fix, DB error surfacing#621

Merged
reachraza merged 6 commits intorcfrom
cue-polish
Mar 26, 2026
Merged

feat(cue): app.startup trigger, pipeline numbering fix, DB error surfacing#621
reachraza merged 6 commits intorcfrom
cue-polish

Conversation

@reachraza
Copy link
Copy Markdown
Contributor

@reachraza reachraza commented Mar 26, 2026

Summary

  • app.startup trigger — New 8th Cue event type that fires once per application launch for initialization workflows (workspace setup, dependency installs, health checks). System-startup semantics: does not re-fire on Cue feature toggle or YAML hot-reload, only on app restart. Full interop with fan-out, filters, chaining, and SSH.
  • Pipeline numbering fixcreatePipeline now derives the next number from the highest existing pipeline name instead of array length, preventing duplicate names after deletions (e.g., deleting Pipeline 2 then creating a new one no longer produces a second "Pipeline 2").
  • DB init error surfacing — Cue database initialization errors are now surfaced to the UI and throttled drag events prevent node vanishing.
  • Misc — Remove unused agentCmd variable (lint warning), add jest-dom vitest type reference to TriggerDrawer test.

Test plan

  • 19 new app.startup tests covering: system boot vs feature toggle, dedup on hot-reload, stop/start persistence, removeSession reset, enabled/agent_id/filter gating, fan-out, chaining, multi-sub, multi-session, payload, sleep prevention, logging
  • 5 new YAML validation tests for app.startup
  • 1 new pipeline numbering test (create → delete → create avoids duplicate names)
  • Updated TriggerDrawer test (6 → 7 trigger types) and usePipelineState test (7 → 8 labels)
  • Full suite: 23,883 tests passing, 0 failures

Summary by CodeRabbit

  • New Features

    • Added an "App Startup" trigger (fires once per application launch) with a UI trigger option, plus a workspace-initialization example and default startup subscription template
  • Bug Fixes

    • Show an error toast when toggling Cue fails
    • Prevent duplicate pipeline names after deleting pipelines
  • Enhancements

    • Improved pipeline editor viewport handling and smoother node dragging
  • Documentation

    • Updated Cue docs and help content to document the new startup event
  • Tests

    • Added/updated tests covering startup behavior and validation

…e vanishing

Problem 1 — Silent Cue enable failure (Linux):
  CueEngine.start() caught better-sqlite3 DB init errors and returned
  silently. The IPC call appeared to succeed, refresh() saw enabled=false,
  and the toggle flipped back with zero feedback. Users on Linux where the
  native binding was missing saw the toggle just snap back to "Disabled"
  with no explanation.

Fix:
  - src/main/cue/cue-engine.ts: change `return` → `throw error` in the
    DB init catch block. Keeps existing Sentry capture and logging, but
    now propagates to the IPC layer so the renderer gets a rejected promise.
  - src/renderer/components/CueModal/CueModal.tsx: add catch block in
    handleToggle that calls notifyToast with the error message. Users now
    see exactly why enabling failed (e.g. "Could not locate the bindings
    file").

Problem 2 — Pipeline nodes vanish during drag (Linux):
  onNodesChange called setPipelineState on every mousemove during drag.
  Each update triggered a full convertToReactFlowNodes recomputation via
  useMemo, creating a tight render loop. On Linux with slower compositing,
  this caused nodes to flicker or disappear entirely.

Fix:
  - src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx:
    buffer drag position updates in a ref and flush once per animation
    frame via requestAnimationFrame. Non-drag position changes (drop,
    programmatic moves) still commit immediately. Cleanup cancels any
    pending frame on unmount.

Tests updated:
  - src/__tests__/main/cue/cue-engine.test.ts: 5 tests that called
    engine.start() expecting silent failure now use expect().toThrow()
  - src/__tests__/main/cue/cue-sleep-wake.test.ts: 1 test updated
    from .not.toThrow() to .toThrow('DB init failed')

Affected surfaces:
  - Cue enable/disable toggle in CueModal (all platforms)
  - Pipeline canvas drag-and-drop (primarily Linux, no regression on macOS)
  - IPC handler cue:enable now returns rejected promise on DB failure
  - No changes to stats-db, build pipeline, or other features
Add `app.startup` as the 8th Cue event type, firing once per application
launch to run initialization workflows (workspace setup, dep installs,
health checks).

System startup semantics:
- Fires only on app launch (isSystemBoot=true), not when toggling Cue on/off
- Deduplicates across YAML hot-reloads via startupFiredKeys Set
- Keys persist across engine stop/start cycles (feature toggling)
- Resets on session removal + next app restart
- Full interop with fan-out, filters, output_prompt, chaining

Pipeline numbering fix:
- createPipeline now derives next number from highest existing pipeline
  name instead of array length, preventing duplicate names after deletion
- Remove unused agentCmd variable in agentStore (lint warning)

22 files changed, 19 new startup tests, 5 yaml validation tests,
1 pipeline numbering test. 23,883 tests passing.
Adds `/// <reference types="@testing-library/jest-dom/vitest" />` to
resolve TS errors for toBeInTheDocument, toHaveStyle, toHaveAttribute
matchers that were missing type augmentation.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

Adds a new app.startup Cue event (fires once per application launch), updates CueEngine startup/boot-scan and deduplication logic, extends types/docs/UI to support the event, and includes tests and editor/UX updates.

Changes

Cohort / File(s) Summary
Documentation
docs/maestro-cue-configuration.md, docs/maestro-cue-events.md, docs/maestro-cue-examples.md
Documented new app.startup event, updated event count to eight, added example subscription and behavioral notes.
Core Engine & Startup
src/main/cue/cue-engine.ts, src/main/index.ts
start() gains isSystemBoot flag; introduces isBootScan and startupFiredKeys dedupe, gate app.startup firing to boot-scan, preserve fired keys across refresh/stop, clear per-session on removal, and call start(true) at boot with try/catch.
Types & Validation
src/main/cue/cue-types.ts, src/shared/cue-pipeline-types.ts, src/main/cue/cue-yaml-loader.ts
Added 'app.startup' to event unions/constants and accepted it in YAML validation.
Tests
src/__tests__/main/cue/cue-startup.test.ts, src/__tests__/main/cue/cue-yaml-loader.test.ts, src/__tests__/main/cue/cue-sleep-wake.test.ts, src/__tests__/renderer/...
Added comprehensive startup behavior tests, validation tests; adjusted existing test expectations and removed a throw-assertion wrapper.
Renderer — Trigger UI & Constants
src/renderer/components/CuePipelineEditor/cueEventConstants.ts, src/renderer/components/CuePipelineEditor/drawers/TriggerDrawer.tsx, src/renderer/components/CuePipelineEditor/panels/triggers/TriggerConfig.tsx, src/renderer/components/CueHelpModal.tsx
Registered app.startup icon/label/color, added trigger item and config messaging, and updated help modal examples.
Renderer — Patterns, Defaults & Prompts
src/renderer/constants/cuePatterns.ts, src/renderer/constants/cueYamlDefaults.ts, src/renderer/hooks/cue/useCueAiChat.ts, src/shared/templateVariables.ts
Added Startup pattern and commented YAML example, updated AI system prompt and template docs to include app.startup.
Pipeline Editor UX / State
src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx, src/renderer/hooks/cue/usePipelineState.ts, src/renderer/components/CuePipelineEditor/utils/pipelineLayout.ts, src/__tests__/renderer/.../pipelineLayout.test.ts
Implemented drag buffering + RAF flush, adjusted multi-pipeline position translation, improved pipeline name generation to avoid duplicates after deletions, and restore pipeline name/color from saved layout.
Minor Renderer Fixes
src/renderer/components/CueModal/CueModal.tsx, src/renderer/stores/agentStore.ts
Added toast error handling around Cue toggle; removed unused agent command lookup.
Tests / Test Helpers
src/__tests__/renderer/.../TriggerDrawer.test.tsx, src/__tests__/renderer/hooks/cue/usePipelineState.test.ts
Updated expected trigger counts, added jest-dom reference, added pipeline creation/deletion name-generation tests and added app.startup to trigger labels.

Sequence Diagram(s)

sequenceDiagram
    participant App as Maestro App
    participant Engine as CueEngine
    participant DB as Cue Database
    participant Loader as YAML Loader
    participant Reconciler as Reconciler
    participant Handler as onCueRun Handler

    App->>Engine: start(isSystemBoot=true)
    activate Engine
    Engine->>DB: initialize()
    DB-->>Engine: ready
    Engine->>Loader: loadCueConfig()
    Loader-->>Engine: subscriptions[]
    Note over Engine: Set isBootScan = true
    Engine->>Engine: initSession(sessionId)
    activate Engine
    alt isBootScan && subscription.enabled && agent/session match
        Engine->>Engine: check startupFiredKeys (sessionId:subName)
        alt not fired
            Engine->>Reconciler: payload/filter match?
            Reconciler-->>Engine: matches
            Engine->>Handler: onCueRun(sessionId, subName, prompt..., payload(reason: "system_startup"))
            Handler-->>Engine: run result
            Engine->>Engine: add to startupFiredKeys
        else already fired
            Engine->>Engine: skip (dedup)
        end
    else disabled or filter mismatch
        Engine->>Engine: skip
    end
    deactivate Engine
    Engine->>Engine: clear isBootScan
    deactivate Engine
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped awake at system start,

one gentle ping to do my part,
A single run, then quiet paws —
startup whispers, no repeat clause. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 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 three main changes: app.startup trigger feature, pipeline numbering fix, and DB error surfacing.

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

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 26, 2026

Greptile Summary

This PR adds a new app.startup Cue event type (fires once per application launch), fixes pipeline duplicate-name bug after deletions, surfaces DB init errors to the UI via toast, and throttles drag events to fix node-vanishing on Linux. The implementation is generally solid with 19 new tests for the startup trigger, but there are two issues worth addressing:

  • Unhandled throw at app startup (src/main/index.ts): start() now throws on DB init failure (changed from a silent return), but the call at boot is not wrapped in try/catch. All other initialization blocks in that function (history manager, stats DB) guard with try/catch and continue gracefully. A DB failure here becomes an unhandled Promise rejection inside app.whenReady().then(async () => {...}), which can crash the Electron process.
  • isSystemBoot flag never resets (cue-engine.ts): The flag is set to true at app launch and is never cleared by stop(). Any session added mid-lifecycle via addSession() will immediately fire its app.startup subscriptions because isSystemBoot === true and the new session's key isn't yet in startupFiredKeys. The docs promise the opposite: "Resets on session removal (so re-adding the session fires again on next app launch)." The missing test scenario is addSession on a running engine after start(true).

All other changes are clean: the pipeline numbering fix is correct, the RAF-throttled drag buffering includes a proper unmount cleanup, error toasts in CueModal are wired correctly, and the unused agentCmd removal is safe.

Confidence Score: 4/5

Safe to merge after adding a try/catch around cueEngine.start(true) in index.ts; the isSystemBoot flag issue is a behavioral edge case worth tracking but does not break the primary user path.

The PR is well-tested (19 startup tests, full suite green) and the core feature logic is sound. One targeted fix remains: the missing try/catch around the throwing start() call at app boot. The isSystemBoot flag issue is a real semantic inconsistency with the docs but affects only the edge case of adding a brand-new session on a running engine, not the primary startup-fires-once flow.

src/main/index.ts (missing try/catch) and src/main/cue/cue-engine.ts (isSystemBoot never resets)

Important Files Changed

Filename Overview
src/main/index.ts Passes isSystemBoot=true at app launch, but cueEngine.start(true) is not wrapped in try/catch; the method now throws on DB init failure, creating an unhandled rejection risk unlike the surrounding initialization blocks.
src/main/cue/cue-engine.ts Adds app.startup event type with isSystemBoot flag and startupFiredKeys dedup set; changes DB init errors to throw instead of return; isSystemBoot is never reset so mid-lifecycle addSession calls fire startup immediately, contradicting the docs.
src/main/cue/cue-types.ts Adds 'app.startup' to CueEventType union and CUE_EVENT_TYPES array; clean, minimal change.
src/main/cue/cue-yaml-loader.ts Adds app.startup branch to validation; no additional required fields, correctly placed before the unknown-event fallthrough.
src/renderer/hooks/cue/usePipelineState.ts Fixes pipeline numbering by deriving max existing number instead of using array length; adds 'app.startup' to DEFAULT_TRIGGER_LABELS; both changes are correct.
src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx Introduces RAF-throttled drag buffering to prevent node-vanishing on Linux; cleanup effect on unmount is included; pending RAF frame from last dragging event could momentarily overwrite the drag-end position, but this is a minor transient visual artifact.
src/renderer/components/CueModal/CueModal.tsx Adds catch block to surface DB init errors as a toast notification when the user toggles the Cue engine on.
src/renderer/stores/agentStore.ts Removes unused agentCmd variable; clean lint fix with no behavioral change.
src/tests/main/cue/cue-startup.test.ts 530-line new test file covering 19 scenarios for app.startup; comprehensive across enable/disable gating, dedup, fan-out, chaining, multi-session, and payload; no test covers addSession mid-run (the isSystemBoot consistency gap).
src/shared/cue-pipeline-types.ts Mirrors the new 'app.startup' event type into the renderer-accessible shared types.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[app.whenReady] --> B{maestroCue flag?}
    B -- yes --> C["cueEngine.start(true)\nisSystemBoot = true"]
    B -- no --> Z[End]
    C --> D[initCueDb]
    D -- throws --> E["throw error ⚠️\n(no try/catch in index.ts)"]
    D -- ok --> F[enabled = true]
    F --> G[syncSession for each session]
    G --> H{app.startup sub?}
    H -- no --> I[Other event watchers]
    H -- yes --> J{isSystemBoot?}
    J -- false --> K[Skip — user toggle]
    J -- true --> L{firedKey in\nstartupFiredKeys?}
    L -- yes --> M[Skip — dedup]
    L -- no --> N[Add firedKey\nDispatch prompt]
    O[User toggles Cue off] --> P["stop()\nstartupFiredKeys NOT cleared\nisSystemBoot stays true ⚠️"]
    P --> Q["start(false)\nsyncs existing sessions\n→ firedKeys prevent re-fire ✓"]
    Q --> R["addSession while running\n→ syncSession called\n→ isSystemBoot=true + no firedKey\n→ fires immediately ⚠️"]
    S[removeSession] --> T[Clear firedKeys for session]
    T --> U[Next app restart fires again]
Loading

Reviews (1): Last reviewed commit: "fix(test): add jest-dom vitest type refe..." | Re-trigger Greptile

Comment on lines 530 to 533
if (encoreFeatures.maestroCue && cueEngine) {
logger.info('Maestro Cue Encore Feature enabled — starting Cue engine', 'Startup');
cueEngine.start();
cueEngine.start(true);
}
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 Unhandled throw can crash app on startup

cueEngine.start() now throws on DB init failure (changed in this PR from return to throw error), but this call site has no try/catch. Every other initialization block in this function (history manager, stats DB) is wrapped in a try/catch that logs and continues. An uncaught exception in the app.whenReady().then(async () => {...}) callback becomes an unhandled Promise rejection that can crash the Electron process on startup.

Suggested change
if (encoreFeatures.maestroCue && cueEngine) {
logger.info('Maestro Cue Encore Feature enabled — starting Cue engine', 'Startup');
cueEngine.start();
cueEngine.start(true);
}
if (encoreFeatures.maestroCue && cueEngine) {
logger.info('Maestro Cue Encore Feature enabled — starting Cue engine', 'Startup');
try {
cueEngine.start(true);
} catch (error) {
logger.error(`Failed to start Cue engine: ${error}`, 'Startup');
}
}

Comment on lines +152 to 157
/** Enable the engine and scan all sessions for Cue configs.
* @param isSystemBoot Pass `true` only at application launch (index.ts). When false (default),
* app.startup subscriptions will NOT fire — this prevents re-firing when the user toggles Cue on/off. */
start(isSystemBoot = false): void {
if (this.enabled) return;

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 isSystemBoot never resets — mid-lifecycle addSession fires startup immediately

isSystemBoot is set to true at app launch and is never reset to false (not in stop(), not anywhere). This means any session added via addSession() while the engine is already running will immediately dispatch its app.startup subscriptions on the same call to syncSession — because isSystemBoot === true and the new session's key is absent from startupFiredKeys.

The documentation explicitly states: "Resets on session removal (so re-adding the session fires again on next app launch)." Under the current logic, re-adding (or newly adding) a session mid-run fires startup right away, contradicting that guarantee.

A simple fix is to not use isSystemBoot as a long-lived flag and instead pass the boot context into syncSession/loadSessionInner only for the sessions that are present at actual boot time:

// In start():
const bootSessions = isSystemBoot ? new Set(this.sessions.keys()) : new Set<string>();
// Pass bootSessions down to loadSessionInner, and only fire app.startup
// when the session id is in bootSessions.

This scopes the "fire on boot" logic to the sessions that existed at startup and avoids the always-true flag leaking into later addSession calls.

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: 5

Caution

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

⚠️ Outside diff range comments (2)
src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx (1)

306-360: ⚠️ Potential issue | 🟠 Major

Cancel the pending drag frame before applying the final drop position.

If the non-dragging position event arrives before the scheduled RAF flush runs, the stale buffered coordinates can still flush afterward and overwrite the final drop location. Merge or clear the buffer before entering the non-drag path.

Possible fix
 			if (isDragging && positionUpdates.size > 0) {
 				if (!dragBufferRef.current) dragBufferRef.current = new Map();
 				for (const [id, pos] of positionUpdates) {
 					dragBufferRef.current.set(id, pos);
 				}
 				if (rafIdRef.current === null) {
 					rafIdRef.current = requestAnimationFrame(flushDragBuffer);
 				}
 				return;
 			}
+
+			if (rafIdRef.current !== null) {
+				cancelAnimationFrame(rafIdRef.current);
+				rafIdRef.current = null;
+			}
+			for (const [id, pos] of dragBufferRef.current ?? []) {
+				if (!positionUpdates.has(id)) positionUpdates.set(id, pos);
+			}
+			dragBufferRef.current = null;
 
 			if (positionUpdates.size > 0) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx` around lines
306 - 360, Before applying the non-drag positionUpdates path, cancel any pending
RAF and merge/clear the drag buffer so stale buffered coordinates cannot flush
afterward: if rafIdRef.current is not null, call
cancelAnimationFrame(rafIdRef.current) and set rafIdRef.current = null, then if
dragBufferRef.current exists merge its entries into the current positionUpdates
(or clear it) so setPipelineState sees the final positions; ensure this logic
runs just before the existing positionUpdates.size > 0 branch (affecting
isDragging, dragBufferRef, rafIdRef, flushDragBuffer, and setPipelineState) and
preserve the existing Y-offset adjustment using stableYOffsetsRef/current
convertToReactFlowNodes semantics.
src/main/cue/cue-engine.ts (1)

166-175: ⚠️ Potential issue | 🔴 Critical

Boot-time DB failures now crash the Electron main process.

Re-throwing here is fine for the IPC enable path, but the app-launch caller in src/main/index.ts (Lines 528-559) still invokes cueEngine.start(true) without a catch. A DB init failure will now abort startup before the renderer can surface anything to the user.

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

In `@src/main/cue/cue-engine.ts` around lines 166 - 175, The catch block in
CueEngine initialization currently re-throws the DB init error (in the catch
inside cue-engine.ts), which causes the Electron main process to crash when
called by cueEngine.start(true); stop re-throwing here and instead fail
gracefully: after logging and captureException, return a rejected/failed start
result or false (or set an internal failed state) so the caller
(cueEngine.start) can handle it without aborting the whole process; update the
catch in the async DB init path (the block that logs "[CUE] Failed to initialize
Cue database — engine will not start" and calls captureException) to remove the
final "throw error" and return/resolve a safe failure value and ensure
CueEngine.start(true) will not crash the process.
🧹 Nitpick comments (1)
src/renderer/components/CueModal/CueModal.tsx (1)

103-108: Fallback error message doesn't account for disable operation.

The fallback 'Failed to enable Cue engine' is used even when the user is attempting to disable Cue (when isEnabled is true). While the fallback is rarely hit since DB errors are Error instances, the message should be context-aware for correctness.

♻️ Proposed fix
 		} catch (err) {
 			notifyToast({
 				type: 'error',
 				title: 'Cue',
-				message: err instanceof Error ? err.message : 'Failed to enable Cue engine',
+				message: err instanceof Error ? err.message : `Failed to ${isEnabled ? 'disable' : 'enable'} Cue engine`,
 			});
 		} finally {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/CueModal/CueModal.tsx` around lines 103 - 108, The
catch block in CueModal.tsx calls notifyToast with a fallback message that
always says "Failed to enable Cue engine"; update that fallback to be
context-aware using the existing isEnabled flag (when isEnabled is true the user
is disabling, so use "Failed to disable Cue engine", otherwise "Failed to enable
Cue engine") in the notifyToast message inside the catch (where notifyToast is
called) so the ternary becomes err instanceof Error ? err.message : (isEnabled ?
'Failed to disable Cue engine' : 'Failed to enable Cue engine').
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/maestro-cue-configuration.md`:
- Line 60: The doc has inconsistent event count references: the table row for
the `event` field currently states "One of the eight event types" while the
"Validation" section still references seven; update both places to the correct
canonical count (either seven or eight) so they match, and verify the linked
resource referenced by ./maestro-cue-events matches that same count; edit the
table line containing `event` and the "Validation" section text to the agreed
number and ensure the wording is consistent across the document.

In `@docs/maestro-cue-events.md`:
- Line 13: The documentation line claiming "Required fields: None beyond the
universal `name`, `event`, and `prompt`" is inaccurate because `prompt_file` is
a valid alternative to `prompt` (e.g., `app.startup` uses `prompt_file` without
inline `prompt`); update that sentence to list `prompt` or `prompt_file` as the
universal field(s) (for example: "`name`, `event`, and either `prompt` or
`prompt_file`") and ensure examples like `app.startup` are consistent with this
wording so readers know `prompt_file` is acceptable.

In `@src/main/cue/cue-engine.ts`:
- Around line 158-160: The field this.isSystemBoot is being set true on any
start(true) call and left true for the process, causing later
refreshSession/app.startup handlers to behave like boot-time init; change the
logic so the "system boot" flag only applies to the initial startup scan: record
whether the engine has already seen a boot (e.g. add or use a hasBooted boolean)
and set this.isSystemBoot = true only when isSystemBoot is true AND hasBooted is
false (or set this.isSystemBoot to true temporarily for the startup flow then
immediately clear it and set hasBooted = true); update the same pattern for the
other boot-setting site referenced (the code around the start/boot flow at the
other block) so subsequent refreshSession(), re-adds, or app.startup
subscriptions do not see isSystemBoot as true.

In `@src/main/index.ts`:
- Line 532: Wrap the call to cueEngine.start(true) in a try-catch so a thrown
error does not block app startup; catch the error, log it (using the existing
logger/processLogger) and continue startup so the window and other subsystems
are created, leaving the cueEngine instance intact for later retries via the
"cue:enable" IPC handler; ensure the catch does not swallow errors silently but
records enough context to diagnose initialization failures.

In `@src/renderer/components/CueHelpModal.tsx`:
- Around line 100-103: The "Startup" example in the CueHelpModal component shows
an invalid subscription shape by omitting the globally required
prompt/prompt_file fields; update the example code block inside the CueHelpModal
(the Startup example near the paragraph with className "mt-1") to include either
prompt or prompt_file in the YAML so it validates, and optionally clarify the
adjacent text if necessary (keep the sentence "No additional fields required"
but ensure the example contains the required global prompt/prompt_file to avoid
misleading copy/paste).

---

Outside diff comments:
In `@src/main/cue/cue-engine.ts`:
- Around line 166-175: The catch block in CueEngine initialization currently
re-throws the DB init error (in the catch inside cue-engine.ts), which causes
the Electron main process to crash when called by cueEngine.start(true); stop
re-throwing here and instead fail gracefully: after logging and
captureException, return a rejected/failed start result or false (or set an
internal failed state) so the caller (cueEngine.start) can handle it without
aborting the whole process; update the catch in the async DB init path (the
block that logs "[CUE] Failed to initialize Cue database — engine will not
start" and calls captureException) to remove the final "throw error" and
return/resolve a safe failure value and ensure CueEngine.start(true) will not
crash the process.

In `@src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx`:
- Around line 306-360: Before applying the non-drag positionUpdates path, cancel
any pending RAF and merge/clear the drag buffer so stale buffered coordinates
cannot flush afterward: if rafIdRef.current is not null, call
cancelAnimationFrame(rafIdRef.current) and set rafIdRef.current = null, then if
dragBufferRef.current exists merge its entries into the current positionUpdates
(or clear it) so setPipelineState sees the final positions; ensure this logic
runs just before the existing positionUpdates.size > 0 branch (affecting
isDragging, dragBufferRef, rafIdRef, flushDragBuffer, and setPipelineState) and
preserve the existing Y-offset adjustment using stableYOffsetsRef/current
convertToReactFlowNodes semantics.

---

Nitpick comments:
In `@src/renderer/components/CueModal/CueModal.tsx`:
- Around line 103-108: The catch block in CueModal.tsx calls notifyToast with a
fallback message that always says "Failed to enable Cue engine"; update that
fallback to be context-aware using the existing isEnabled flag (when isEnabled
is true the user is disabling, so use "Failed to disable Cue engine", otherwise
"Failed to enable Cue engine") in the notifyToast message inside the catch
(where notifyToast is called) so the ternary becomes err instanceof Error ?
err.message : (isEnabled ? 'Failed to disable Cue engine' : 'Failed to enable
Cue engine').
🪄 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: e46087db-9f07-4e5c-afcd-3576c2b5e369

📥 Commits

Reviewing files that changed from the base of the PR and between 39fd85d and 17ba2b6.

📒 Files selected for processing (26)
  • docs/maestro-cue-configuration.md
  • docs/maestro-cue-events.md
  • docs/maestro-cue-examples.md
  • src/__tests__/main/cue/cue-engine.test.ts
  • src/__tests__/main/cue/cue-sleep-wake.test.ts
  • src/__tests__/main/cue/cue-startup.test.ts
  • src/__tests__/main/cue/cue-yaml-loader.test.ts
  • src/__tests__/renderer/components/CuePipelineEditor/drawers/TriggerDrawer.test.tsx
  • src/__tests__/renderer/hooks/cue/usePipelineState.test.ts
  • src/main/cue/cue-engine.ts
  • src/main/cue/cue-types.ts
  • src/main/cue/cue-yaml-loader.ts
  • src/main/index.ts
  • src/renderer/components/CueHelpModal.tsx
  • src/renderer/components/CueModal/CueModal.tsx
  • src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx
  • src/renderer/components/CuePipelineEditor/cueEventConstants.ts
  • src/renderer/components/CuePipelineEditor/drawers/TriggerDrawer.tsx
  • src/renderer/components/CuePipelineEditor/panels/triggers/TriggerConfig.tsx
  • src/renderer/constants/cuePatterns.ts
  • src/renderer/constants/cueYamlDefaults.ts
  • src/renderer/hooks/cue/useCueAiChat.ts
  • src/renderer/hooks/cue/usePipelineState.ts
  • src/renderer/stores/agentStore.ts
  • src/shared/cue-pipeline-types.ts
  • src/shared/templateVariables.ts
💤 Files with no reviewable changes (1)
  • src/renderer/stores/agentStore.ts

| -------- | ------ | ---------------------------------------------------------------------- |
| `name` | string | Unique identifier. Used in logs, history, and as a reference in chains |
| `event` | string | One of the seven [event types](./maestro-cue-events) |
| `event` | string | One of the eight [event types](./maestro-cue-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.

⚠️ Potential issue | 🟡 Minor

Event count wording is now inconsistent within this doc.

Line 60 says eight event types, but the Validation section still says seven. Please align both references to avoid contradictory docs.

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

In `@docs/maestro-cue-configuration.md` at line 60, The doc has inconsistent event
count references: the table row for the `event` field currently states "One of
the eight event types" while the "Validation" section still references seven;
update both places to the correct canonical count (either seven or eight) so
they match, and verify the linked resource referenced by ./maestro-cue-events
matches that same count; edit the table line containing `event` and the
"Validation" section text to the agreed number and ensure the wording is
consistent across the document.

Engine:
- Replace persistent isSystemBoot with scoped isBootScan that is
  set true only during the start() session scan and cleared immediately
  after, preventing later refreshSession/auto-discovery from firing
  app.startup subs
- DB init failure now returns gracefully instead of throwing, so the
  Electron main process is never blocked at startup
- Wrap cueEngine.start(true) in try-catch at index.ts boot site

UI:
- CueModal toggle error toast is now context-aware (enable vs disable)
- CueHelpModal startup quick-reference example includes prompt field
- CuePipelineEditor cancels pending RAF and merges drag buffer before
  applying non-drag position updates to prevent stale flush

Docs:
- Fix inconsistent event count in configuration validation section
- Clarify prompt_file as alternative to prompt in events doc
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.

🧹 Nitpick comments (1)
src/main/index.ts (1)

532-539: Consider adding Sentry capture for unexpected boot failures.

The try/catch correctly prevents Cue initialization failures from blocking app startup. However, while DB init errors are already captured in cue-engine.ts, any other unexpected errors thrown from start() (e.g., future code changes) would only be logged here without Sentry visibility.

Per coding guidelines, unexpected errors should reach Sentry. Consider adding captureException for non-recoverable errors:

♻️ Optional: Add Sentry capture for completeness
 	if (encoreFeatures.maestroCue && cueEngine) {
 		logger.info('Maestro Cue Encore Feature enabled — starting Cue engine', 'Startup');
 		try {
 			cueEngine.start(true);
 		} catch (err) {
 			logger.error(
 				`Cue engine failed to start at boot — will remain available for retry via Settings: ${err}`,
 				'Startup'
 			);
+			// Report to Sentry for visibility into non-DB-init failures
+			import('./utils/sentry').then(({ captureException }) => {
+				captureException(err instanceof Error ? err : new Error(String(err)), {
+					extra: { operation: 'cue.startBoot' },
+				});
+			});
 		}
 	}

Note: This is optional since DB init errors (the most likely failure) are already captured in cue-engine.ts. This would catch edge cases from future code changes.

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

In `@src/main/index.ts` around lines 532 - 539, Add a Sentry capture alongside the
existing logger.error inside the catch for cueEngine.start(true) so unexpected
boot-time exceptions are sent to Sentry; update the catch block that currently
calls logger.error(`Cue engine failed to start... ${err}`, 'Startup') to also
call Sentry.captureException(err) (or the app's sentry client) after ensuring
Sentry is imported/initialized and that the captured value is the original error
object rather than a string so stack/context are preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/index.ts`:
- Around line 532-539: Add a Sentry capture alongside the existing logger.error
inside the catch for cueEngine.start(true) so unexpected boot-time exceptions
are sent to Sentry; update the catch block that currently calls
logger.error(`Cue engine failed to start... ${err}`, 'Startup') to also call
Sentry.captureException(err) (or the app's sentry client) after ensuring Sentry
is imported/initialized and that the captured value is the original error object
rather than a string so stack/context are preserved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: df46670c-68f2-4c81-bce3-e4607772f618

📥 Commits

Reviewing files that changed from the base of the PR and between 17ba2b6 and 78ad7e7.

📒 Files selected for processing (8)
  • docs/maestro-cue-configuration.md
  • docs/maestro-cue-events.md
  • src/__tests__/main/cue/cue-sleep-wake.test.ts
  • src/main/cue/cue-engine.ts
  • src/main/index.ts
  • src/renderer/components/CueHelpModal.tsx
  • src/renderer/components/CueModal/CueModal.tsx
  • src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx
✅ Files skipped from review due to trivial changes (3)
  • docs/maestro-cue-configuration.md
  • src/renderer/components/CueHelpModal.tsx
  • docs/maestro-cue-events.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tests/main/cue/cue-sleep-wake.test.ts
  • src/renderer/components/CueModal/CueModal.tsx

- mergePipelinesWithSavedLayout now restores saved color and name from
  the layout file when pipeline IDs match, preventing colors/names from
  being re-derived by parse order on every reload
- Add fitView effect on selectedPipelineId change so switching from
  All Pipelines to a single pipeline (or vice versa) auto-centers the
  viewport on the visible nodes instead of leaving it at the stacked
  Y-offset position
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

🤖 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/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx`:
- Around line 241-251: The effect in CuePipelineEditor that calls
reactFlowInstance.fitView on selection changes is overwriting a persisted
viewport restored by usePipelineLayout; change the useEffect (which references
prevSelectedIdRef, pipelineState.selectedPipelineId, reactFlowInstance) to skip
the auto-fit for the initial hydrated selection: add a simple guard/ref (e.g.,
hasHydratedSelectionRef or check pipelineLayout.viewport from usePipelineLayout)
so that when the editor mounts and a saved viewport exists you do not call
fitView, and only allow the delayed fitView for subsequent user-initiated
pipeline switches; set the guard after the first selection change to re-enable
auto-fit thereafter.
🪄 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: 665dae6c-db14-47d1-aaf7-fb11dca5e9b2

📥 Commits

Reviewing files that changed from the base of the PR and between 78ad7e7 and 309d589.

📒 Files selected for processing (3)
  • src/__tests__/renderer/components/CuePipelineEditor/utils/pipelineLayout.test.ts
  • src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx
  • src/renderer/components/CuePipelineEditor/utils/pipelineLayout.ts
✅ Files skipped from review due to trivial changes (1)
  • src/tests/renderer/components/CuePipelineEditor/utils/pipelineLayout.test.ts

…port

Add hasHydratedSelectionRef guard so the fitView effect skips the
initial selectedPipelineId change from layout restoration, preventing
it from overwriting the saved viewport restored by usePipelineLayout.
Subsequent user-initiated pipeline switches still trigger fitView.
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

♻️ Duplicate comments (1)
src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx (1)

243-260: ⚠️ Potential issue | 🟡 Minor

This still skips the first real pipeline switch.

If the editor opens in “All Pipelines” and there is no layout-driven selection hydration, the first user selection also falls into the Line 250 skip branch, so fitView() never runs for that switch. The guard needs to arm on the initial render/load state, not on the first selectedPipelineId change.

Possible fix
 const prevSelectedIdRef = useRef(pipelineState.selectedPipelineId);
 const hasHydratedSelectionRef = useRef(false);
 useEffect(() => {
-	if (prevSelectedIdRef.current === pipelineState.selectedPipelineId) return;
-	prevSelectedIdRef.current = pipelineState.selectedPipelineId;
-
-	// Skip the initial hydration — let usePipelineLayout restore the saved viewport
 	if (!hasHydratedSelectionRef.current) {
-		hasHydratedSelectionRef.current = true;
+		hasHydratedSelectionRef.current = pipelineState.pipelines.length > 0;
+		prevSelectedIdRef.current = pipelineState.selectedPipelineId;
 		return;
 	}
 
+	if (prevSelectedIdRef.current === pipelineState.selectedPipelineId) return;
+	prevSelectedIdRef.current = pipelineState.selectedPipelineId;
+
 	// Short delay so ReactFlow has rendered the new node set before fitting
 	const timer = setTimeout(() => {
 		reactFlowInstance.fitView({ padding: 0.15, duration: 200 });
 	}, 50);
 	return () => clearTimeout(timer);
-}, [pipelineState.selectedPipelineId, reactFlowInstance]);
+}, [pipelineState.pipelines.length, pipelineState.selectedPipelineId, reactFlowInstance]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx` around lines
243 - 260, The current effect uses hasHydratedSelectionRef to skip the very
first selection change, but that also skips the first real user switch when the
editor starts in “All Pipelines”; change the guard so it only skips the initial
render/load, not the first subsequent selection change: use prevSelectedIdRef
(which stores pipelineState.selectedPipelineId) to detect initial uninitialized
state (e.g., prevSelectedIdRef.current == null/undefined) and on that first run
set prevSelectedIdRef.current and return, otherwise call
reactFlowInstance.fitView on subsequent pipelineState.selectedPipelineId
changes; update the useEffect that references prevSelectedIdRef,
hasHydratedSelectionRef, pipelineState.selectedPipelineId, and
reactFlowInstance.fitView accordingly and remove or repurpose
hasHydratedSelectionRef.
🤖 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/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx`:
- Around line 278-305: The flushDragBuffer handler applies buffered node
positions using stableYOffsetsRef.current, but those offsets can be stale after
the final drop; modify flushDragBuffer to, after creating newPipelines (the
updated nodes), call computePipelineYOffsets(newPipelines) to recompute the
stacked Y offsets and update stableYOffsetsRef.current (and any derived state)
so downstream pipelines are re-spaced; do this only in flushDragBuffer (the
final non-drag commit) so offsets remain frozen during drag but refresh once
after the drop. Include references: flushDragBuffer, stableYOffsetsRef,
computePipelineYOffsets, and the setPipelineState update that builds
newPipelines.

---

Duplicate comments:
In `@src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx`:
- Around line 243-260: The current effect uses hasHydratedSelectionRef to skip
the very first selection change, but that also skips the first real user switch
when the editor starts in “All Pipelines”; change the guard so it only skips the
initial render/load, not the first subsequent selection change: use
prevSelectedIdRef (which stores pipelineState.selectedPipelineId) to detect
initial uninitialized state (e.g., prevSelectedIdRef.current == null/undefined)
and on that first run set prevSelectedIdRef.current and return, otherwise call
reactFlowInstance.fitView on subsequent pipelineState.selectedPipelineId
changes; update the useEffect that references prevSelectedIdRef,
hasHydratedSelectionRef, pipelineState.selectedPipelineId, and
reactFlowInstance.fitView accordingly and remove or repurpose
hasHydratedSelectionRef.
🪄 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: 4413b952-5bf6-4af1-b396-f3ad151e08d1

📥 Commits

Reviewing files that changed from the base of the PR and between 309d589 and 75edbdf.

📒 Files selected for processing (1)
  • src/renderer/components/CuePipelineEditor/CuePipelineEditor.tsx

@reachraza reachraza merged commit 1b6a298 into rc Mar 26, 2026
4 checks passed
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