# @fusion/dashboard ## 0.41.0 ### Patch Changes - @fusion/core@0.41.0 - @fusion/engine@0.41.0 - @fusion/i18n@0.39.3 - @fusion-plugin-examples/cli-printing-press@0.1.20 - @fusion-plugin-examples/compound-engineering@0.1.3 - @fusion-plugin-examples/dependency-graph@0.1.34 - @fusion-plugin-examples/roadmap@0.1.22 - @fusion-plugin-examples/cursor-runtime@0.1.22 - @fusion-plugin-examples/droid-runtime@0.1.29 - @fusion-plugin-examples/hermes-runtime@0.2.53 - @fusion-plugin-examples/openclaw-runtime@0.2.53 - @fusion-plugin-examples/paperclip-runtime@0.2.53 ## 0.40.1 ### Patch Changes - @fusion/core@0.40.1 - @fusion/engine@0.40.1 - @fusion/i18n@0.39.2 - @fusion-plugin-examples/cli-printing-press@0.1.19 - @fusion-plugin-examples/compound-engineering@0.1.2 - @fusion-plugin-examples/dependency-graph@0.1.33 - @fusion-plugin-examples/roadmap@0.1.21 - @fusion-plugin-examples/cursor-runtime@0.1.21 - @fusion-plugin-examples/droid-runtime@0.1.28 - @fusion-plugin-examples/hermes-runtime@0.2.52 - @fusion-plugin-examples/openclaw-runtime@0.2.52 - @fusion-plugin-examples/paperclip-runtime@0.2.52 ## 0.40.0 ### Patch Changes - 2d2024f: Unfreeze dashboard spinners and pulse/enter animations. Transition tokens (`--transition-slow: 0.3s ease`) bundle a duration and an easing; 15 animation declarations reused them as bare durations, which made the whole `animation` declaration invalid at computed-value time and silently resolved it to `animation: none`. Animation rules now use new duration-only tokens (`--duration-instant/fast/normal/slow`), with the transition tokens derived from them, and a repo-wide CSS regression test forbids the pattern. - 784f308: Fix first tap of GitHub tracking icon in quick task entry on mobile (FN-6148). The delegated touch handler on `.quick-entry-actions` now uses `closest("button")` to resolve taps that land on child SVG elements, so the GitHub tracking toggle responds correctly on the first touch — identical root-cause fix as FN-6145. - @fusion/core@0.40.0 - @fusion/engine@0.40.0 - @fusion/i18n@0.39.1 - @fusion-plugin-examples/cli-printing-press@0.1.18 - @fusion-plugin-examples/compound-engineering@0.1.1 - @fusion-plugin-examples/dependency-graph@0.1.32 - @fusion-plugin-examples/roadmap@0.1.20 - @fusion-plugin-examples/cursor-runtime@0.1.20 - @fusion-plugin-examples/droid-runtime@0.1.27 - @fusion-plugin-examples/hermes-runtime@0.2.51 - @fusion-plugin-examples/openclaw-runtime@0.2.51 - @fusion-plugin-examples/paperclip-runtime@0.2.51 ## 0.39.0 ### Patch Changes - @fusion-plugin-examples/cli-printing-press@0.1.17 - @fusion-plugin-examples/cursor-runtime@0.1.19 - @fusion-plugin-examples/dependency-graph@0.1.31 - @fusion-plugin-examples/droid-runtime@0.1.26 - @fusion-plugin-examples/hermes-runtime@0.2.50 - @fusion-plugin-examples/openclaw-runtime@0.2.50 - @fusion-plugin-examples/paperclip-runtime@0.2.50 - @fusion-plugin-examples/roadmap@0.1.19 - @fusion/core@0.39.0 - @fusion/engine@0.39.0 ## 0.38.1 ### Patch Changes - bad8f52: Improve the mission manager mobile stacked layout so mission rows reflow cleanly: stacked mission list items switch to a column layout with stretched content, item actions become full-width and wrap instead of cramped inline controls, and run controls span the full width. - @fusion-plugin-examples/cli-printing-press@0.1.16 - @fusion-plugin-examples/dependency-graph@0.1.30 - @fusion-plugin-examples/roadmap@0.1.18 - @fusion/core@0.38.1 - @fusion/engine@0.38.1 - @fusion-plugin-examples/cursor-runtime@0.1.18 - @fusion-plugin-examples/droid-runtime@0.1.25 - @fusion-plugin-examples/hermes-runtime@0.2.49 - @fusion-plugin-examples/openclaw-runtime@0.2.49 - @fusion-plugin-examples/paperclip-runtime@0.2.49 ## 0.38.0 ### Patch Changes - Updated dependencies [9112b7d] - @fusion/engine@0.38.0 - @fusion-plugin-examples/cli-printing-press@0.1.15 - @fusion-plugin-examples/dependency-graph@0.1.29 - @fusion-plugin-examples/roadmap@0.1.17 - @fusion/core@0.38.0 - @fusion-plugin-examples/cursor-runtime@0.1.17 - @fusion-plugin-examples/droid-runtime@0.1.24 - @fusion-plugin-examples/hermes-runtime@0.2.48 - @fusion-plugin-examples/openclaw-runtime@0.2.48 - @fusion-plugin-examples/paperclip-runtime@0.2.48 ## 0.37.0 ### Patch Changes - @fusion/core@0.37.0 - @fusion/engine@0.37.0 - @fusion-plugin-examples/cli-printing-press@0.1.14 - @fusion-plugin-examples/dependency-graph@0.1.28 - @fusion-plugin-examples/roadmap@0.1.16 - @fusion-plugin-examples/cursor-runtime@0.1.16 - @fusion-plugin-examples/droid-runtime@0.1.23 - @fusion-plugin-examples/hermes-runtime@0.2.47 - @fusion-plugin-examples/openclaw-runtime@0.2.47 - @fusion-plugin-examples/paperclip-runtime@0.2.47 ## 0.36.0 ### Patch Changes - @fusion/core@0.36.0 - @fusion/engine@0.36.0 - @fusion-plugin-examples/cli-printing-press@0.1.13 - @fusion-plugin-examples/dependency-graph@0.1.27 - @fusion-plugin-examples/roadmap@0.1.15 - @fusion-plugin-examples/cursor-runtime@0.1.15 - @fusion-plugin-examples/droid-runtime@0.1.22 - @fusion-plugin-examples/hermes-runtime@0.2.46 - @fusion-plugin-examples/openclaw-runtime@0.2.46 - @fusion-plugin-examples/paperclip-runtime@0.2.46 ## 0.35.0 ### Patch Changes - Updated dependencies [1992049] - @fusion/engine@0.35.0 - @fusion-plugin-examples/cli-printing-press@0.1.12 - @fusion-plugin-examples/dependency-graph@0.1.26 - @fusion-plugin-examples/roadmap@0.1.14 - @fusion/core@0.35.0 - @fusion-plugin-examples/cursor-runtime@0.1.14 - @fusion-plugin-examples/droid-runtime@0.1.21 - @fusion-plugin-examples/hermes-runtime@0.2.45 - @fusion-plugin-examples/openclaw-runtime@0.2.45 - @fusion-plugin-examples/paperclip-runtime@0.2.45 ## 0.34.0 ### Patch Changes - 6a6c6fd: Dashboard startup and request-storm fixes: - **Faster startup**: parallelized independent store inits, started CentralCore init early in background, and ran plugin loading concurrently with extension resolution. The duplicate-runtime root cause is also fixed — `shouldUseHybridExecutor` no longer auto-enables for local-only multi-project setups, where `ProjectEngineManager` already handles project lifecycle (set `FUSION_HYBRID_EXECUTOR=1` to force-enable). Eliminates ~7s of redundant self-healing pipeline work per cold start. - **Per-page request reduction**: added in-flight request dedupe (`packages/dashboard/app/api/dedupe.ts`) wrapped around the top API offenders. A single page load went from ~177 requests to ~101, with `/api/plugins/ui-slots` dropping from 17× to 1×. - **Stale-data-after-mutation hazard**: `forceFresh` option on the deduped fetchers now redirects ALL in-flight waiters to receive the fresh post-mutation response, not just the forcing caller. Generation counters in `useAgents` and `AgentListModal` provide a second layer of protection against slow polls overwriting fresh state. - **SSE refresh storm**: agent SSE event handler now debounces (250ms) with a trailing-edge guard, so multi-agent activity bursts coalesce to at most 2 refetches per burst instead of one per event. - **Live isolation-mode transition**: PATCH `/api/projects/:id` with an `isolationMode` change now returns a 503 with actionable guidance when HybridExecutor is unavailable (local-only single-node), instead of silently persisting a config that the live runtime won't honor. - **Error handling regression**: restored try/catch around `HybridExecutor.initialize` and `engineManager.ensureEngine` in the parallel engine setup so a paused or broken cwd project no longer aborts dashboard startup. - **TaskStore migration race**: sequenced the SQLite store inits (TaskStore → AutomationStore → PluginStore → AgentStore) since they all open the same `.fusion/fusion.db` and run `addColumnIfMissing` migrations with a TOCTOU `hasColumn` → `ALTER` pattern. - **`gh` CLI invocation storm**: `isGhAvailable()` and `isGhAuthenticated()` now memoize their results with a 60s TTL. `GitHubTrackingReconciler` was scanning up to 200 done tasks at startup and calling `hasGhAuth()` per task — each call shelled out to `gh --version` and `gh auth status` (which makes a network roundtrip), pinning the event loop for ~60s of synchronous `spawnSync` work. CPU-profile-confirmed: dropped from 71s (69% of cold-start CPU) to 2s. The cache benefits all 28+ call sites in `dashboard/src/github.ts`, the engine PR monitor, the research provider, and the API routes automatically. `resetGhAvailabilityCache()` is exported for login/logout flows that need to invalidate immediately. - **SQLite integrity check delay**: `PRAGMA integrity_check(100)` walks every page of the database file and was scheduled 3 seconds after init — landing right in the responsiveness-critical window for ~7s per database. Pushed the deferred-check timer to 60 seconds so the user is already interacting with the dashboard by the time it runs. The check itself is unchanged; corruption detection still works. - **Engine init event-loop yields**: `InProcessRuntime.start()` now awaits a `setImmediate`-based yield between major init phases (TaskStore → Plugins → WorktreePool → AgentStore → Scheduler → Executor → HeartbeatMonitor → SelfHealing) so HTTP requests can be processed between them instead of waiting on the entire stack. Same yield is now interleaved between each step of `SelfHealingManager.runStartupRecovery()` (34 steps per project) and its periodic maintenance batches. - **Deferred startup recovery**: `InProcessRuntime.start()` no longer awaits `resumeStartupRecoverySequence()` or `workerManager.reconcileOrphaned()` — both are correctness-preserving background operations and their git/SQLite work was blocking server-listen for several seconds. - **Deferred orphan-task AI agent resumption**: orphaned in-progress tasks resumed at engine restart now wait 30 seconds before spawning their AI agent session (worktree setup + pi-coding-agent session creation is heavy and saturates the event loop). Override via `FUSION_RESUME_ORPHAN_DELAY_MS=`; auto-zeroes under Vitest. - **Event-loop lag tracer**: opt-in debug aid for diagnosing cold-start regressions. Set `FUSION_TRACE_EL_LAG=/path/to/file.txt` to capture every block >150ms with a timestamp relative to process start. - Updated dependencies [6a6c6fd] - Updated dependencies [97f1143] - Updated dependencies [4e4830f] - @fusion/engine@0.34.0 - @fusion/core@0.34.0 - @fusion-plugin-examples/cli-printing-press@0.1.11 - @fusion-plugin-examples/dependency-graph@0.1.25 - @fusion-plugin-examples/roadmap@0.1.13 - @fusion-plugin-examples/cursor-runtime@0.1.13 - @fusion-plugin-examples/droid-runtime@0.1.20 - @fusion-plugin-examples/hermes-runtime@0.2.44 - @fusion-plugin-examples/openclaw-runtime@0.2.44 - @fusion-plugin-examples/paperclip-runtime@0.2.44 ## 0.33.0 ### Minor Changes - 6e7f1e5: feat(dashboard): explain "Recent integration-branch advances" and add a one-click "Sync working tree" fix Two additions to Git Manager → Status: **Info disclosure** — an `[i]` button next to the "Recent integration-branch advances (N need action)" header toggles an inline explainer. Covers what an "advance" is, what each `autoSyncOutcome` value means (`clean-sync`, `synced-with-edits-restored`, `off / not run`, `stash-failed`, `would-conflict`, …), and where to enable `mergeAdvanceAutoSync` for the permanent fix. **Sync working tree button** — when ≥1 advance shows `needsAction`, a button surfaces in the same header that calls the existing `POST /api/git/pull` (FN-5358 Smart Pull machinery: auto-stash dirty edits, fast-forward pull, restore stash, surface conflicts). On success the extended git status auto-refetches and the "need action" count drops; on conflict, the existing error toast fires. No new state machine — `handlePull`/`remoteLoading === "pull"` is the same plumbing the existing Pull button uses. - 85786e7: feat(dashboard): show extended integration-branch + working-tree state in Git Manager Repository Status panel now answers "what is the actual state of my project root vs the integration branch?" so operators can be sure of the picture even when the Merge Advance Notice banner has been dismissed. `GET /api/git/status` accepts a new `?extended=1` query and returns additional optional fields: - **integrationBranch** + **integrationBranchSource** — the canonical branch (resolved via `settings.integrationBranch` → legacy `baseBranch` → `origin/HEAD` → `main`) and where the value came from. - **integrationTipSha / originIntegrationTipSha** — SHAs at both ends, so operators can spot when local main has been advanced by the merger but origin/main hasn't caught up. - **aheadOfIntegration / behindIntegration** — HEAD vs local integration tip (useful when on a non-integration branch). - **aheadOfOriginIntegration / behindOriginIntegration** — local integration tip vs `origin/`. - **dirtyDetails** — staged/modified/untracked/conflicted counts + a 12-line porcelain sample. - **indexStaleVsHead** — true when the index reflects a previous tip and the worktree is clean against the index but not against HEAD. Surfaces the exact "phantom staged changes" scenario that `mergeAdvanceAutoSync` exists to fix. - **stashCount** — for at-a-glance recovery awareness. - **recentMergeAdvances** — up to 5 recent `merge:integration-ref-advance` audit events for the project root, joined with their `merge:auto-sync` outcomes; entries whose auto-sync didn't successfully bring this worktree forward are flagged `needsAction: true`. `GitManagerModal` now renders all of this: - The existing Branch / Commit / Working Tree / Remote Sync cards gain sub-text — Working Tree shows staged/modified/untracked/conflicted breakdown; Branch shows whether you're on the integration branch. - A second row of cards adds Integration branch (with resolution source + tip SHA), HEAD-vs-integration ahead/behind, local-integration-vs-origin ahead/behind, and stash count. - A yellow warning panel appears when `indexStaleVsHead` is true, telling the operator to enable `mergeAdvanceAutoSync` or run `git reset --hard HEAD`. - A "Recent integration-branch advances" list shows the last few merger advances with their per-advance auto-sync outcome, color-coded by whether they still need action. All `fetchGitStatus(projectId)` calls inside `GitManagerModal` now pass `{ extended: true }`. Other callers in the app are unaffected — the extra fields are optional and the un-extended response shape is unchanged. ### Patch Changes - 60a0012: fix(dashboard): stop main-chat and quick-chat composers from instantly dismissing the Android soft keyboard Two layered Android-specific fixes for the chat composers: 1. The body scroll-lock applied while the keyboard is open in main chat was an iOS-specific workaround for visualViewport drift. On Android Chrome it does the opposite of what we want — mutating `body { position: fixed; ... }` while the keyboard is opening causes Chrome to treat it as a focus-target relayout and immediately dismisses the keyboard. `useMobileScrollLock` is now gated to iOS UAs. 2. ChatView and QuickChatFAB both had an iOS-specific `onTouchStart` on the textarea that called `event.preventDefault()` and then programmatically refocused the input (to suppress iOS's visualViewport auto-scroll on re-focus). On Android, `preventDefault` on a textarea touchstart prevents the soft keyboard from opening — programmatic `focus()` alone does not raise the Android keyboard. Result: tapping the composer focused the input but the keyboard never appeared, looking like an instant dismiss. The touchstart workaround is now gated to iOS UAs via `isIOS()`. - a10fc56: fix(dashboard): keep Android keyboard open in main chat; disable kanban pinch-zoom Two Android-specific fixes: 1. **Keyboard dismissing in main chat.** `mobileKeyboardOpen` in `App.tsx` (derived from `useMobileKeyboard`) gates `project-content--with-mobile-nav` / `--with-footer` className assignment and MobileNavBar rendering. When the soft keyboard opened, those classes were removed and the nav unmounted, shrinking padding-bottom by ~80px in a single render. Android Chrome treats the resulting jump of the focused chat input as the focus target moving and instantly dismisses the keyboard. With `interactive-widget=resizes-content` set on Android, the layout viewport itself shrinks with the keyboard, so the hide-nav-on-keyboard behavior was redundant on Android (and harmful). The whole pattern is now gated to iOS via `isIOS()`. iOS path is unchanged. 2. **Pinch-zoom on kanban.** Android Chrome ignores `user-scalable=no` for accessibility, and the kanban board's `overflow-x: auto` columns combined with the inflated ICB produce a broken visual when the user zooms out. Adds `touch-action: pan-x pan-y` to `html, body` inside the mobile media query, which keeps scroll panning but disables pinch-zoom (Chat and MissionManager were unaffected because they don't expose a wide horizontal scrollable region). - de67c51: fix(dashboard): pull syncs the worktree to local integration tip, not just to origin The integration-mode `POST /api/git/pull` (used by the merge-advance-notice banner) only ran `git merge --ff-only origin/` after fetching. When the merger had advanced local `refs/heads/` via `update-ref` but the user hadn't pushed yet, the worktree's HEAD already resolved to the new sha (symbolic ref follow) but the working tree and index were still at the old state. The fast-forward step short-circuited (`already up to date with origin`) and the user saw "Pull completed" with `fromSha === toSha` while their files visibly stayed behind. Pull now explicitly resets the worktree to `refs/heads/` after the origin fast-forward step. The autostash above protects user edits, so the reset is safe regardless of whether the origin FF ran. - 5d35b64: fix(dashboard): remove duplicate integration-advances UI; Sync working tree is now pure-local (no origin fetch) **Removed duplicate UI** — Git Manager → Status had two overlapping sections rendering the same data: a `Sync local tip` button + a `Recent integration advances` list, sitting above the highlighted `Recent integration-branch advances` block (the one with the lost-work warnings). Deleted the duplicate (`gm-integration-actions` + `gm-recent-advances`) along with the dead `mergeAdvanceEvents` state, fetcher, and SSE subscription that only fed it. **Sync working tree is now pure-local** — for the "N need action" case the merger has already advanced `refs/heads/` locally and the worktree just needs to follow. Previously the button called the integration-mode pull which ran `tryFastForwardFromOrigin` first, silently pulling in unrelated remote commits. New `skipOriginFetch` option on `PullGitBranchOptions.integration` (and the matching `POST /api/git/pull` body field) skips the origin step entirely. The Sync button passes `skipOriginFetch: true`, so the sequence is: auto-stash → `git reset --hard refs/heads/` → restore stash. Origin is not touched. Help disclosure updated to match the new behavior. - 4f38ed1: fix(dashboard): clear `needs action` on recent integration-branch advances after manual sync The Git Manager's "Recent integration-branch advances" list derived `needsAction` purely from the original `merge:auto-sync` audit-event outcome. When the operator clicked "Sync working tree" — or fixed up the worktree by hand — the worktree caught up to the integration tip, but the list kept showing "(N need action)" because the historical audit events still recorded the original failure/disabled state. `collectRecentMergeAdvances` now also checks whether each advance's `toSha` is reachable from the current HEAD. If it is, the worktree already contains that advance and `needsAction` is false regardless of what the audit trail recorded. - ef12df4: fix(dashboard): close 8 review findings on extended Git Manager status + Integration branch setting **Settings persistence (data-loss)** — the project-settings patch builder now applies null-as-delete to all non-model keys, matching the global-settings branch. Previously, clearing the Integration branch field (picking `(auto-detect)` or clicking `Use dropdown`) set `integrationBranch: undefined`, which `JSON.stringify` silently dropped — the server retained the stale explicit value and the operator could not un-pin the branch from the UI. **`isIndexStale` was wrong both directions** — the heuristic (`diff --cached --name-only` non-empty AND `diff --name-only` empty) fired false-positive on benign `git add` and false-negative whenever the worktree had any unrelated edit. Replaced with a reflog-anchored check: stale iff `refs/heads/@{1}` exists, HEAD is a descendant of it, and `git diff-index --cached ` is empty (i.e. the index exactly matches the pre-advance state). **Auto-sync attribution** — two fixes to `collectRecentMergeAdvances` in `register-git-github.ts`: - Auto-sync events are now matched by `(taskId, newSha)` instead of `taskId`-only. A task that produced multiple advances over time no longer has all its older entries mislabeled with the most-recent outcome. - `worktreePath` comparison now runs both sides through `fs.realpathSync` first. On macOS the merger emits canonicalized paths (via `canonicalizePath` in `worktree-pool.ts`) while the route was called with the store's raw `rootDir`; symlinked project paths caused every advance to be marked `needsAction: true` indefinitely. **Extended path no longer 500s on git failure** — the `?extended=1` branch wraps `computeExtendedGitStatus` in its own try/catch and falls back to the basic status shape on any unhandled failure. Previously an unguarded `git branch --show-current` throw escaped to the route's outer catch and returned HTTP 500, while the basic path returned 200 with the swallowed-failure shape — surface parity matters because the dashboard always passes `extended=1` and would otherwise render an error toast where it should render the degraded panel. Also wrapped the same call inside `computeExtendedGitStatus` so detached-HEAD / non-git states return an empty `currentBranch` instead of throwing. **Integration branch falls back to `refs/remotes/origin/`** — when the configured branch exists only as a remote-tracking ref (e.g. operator set `integrationBranch: "release/v2"` without ever `git switch`-ing it locally), `integrationTipSha` now resolves to the origin tip instead of being null. A new `integrationTipSource: "local" | "remote-only" | "missing"` field tells the UI which side won; the Git Manager surfaces this with a `(remote-only — run git switch to track locally)` sub-text and a `no ref found` error state when both refs are missing. **Copy commit hash shows two buttons** — the Copy button now copies `status.commit` (the short SHA actually displayed in the `` element). A second Copy-full button surfaces `status.headSha` for git operations that need the 40-char SHA. Previously the single button silently copied the full SHA when extended was on, so what the user saw on screen was no longer what they pasted. **Detached HEAD no longer shows misleading "(not on main)"** — `git branch --show-current` returns empty on detached HEAD; the route now leaves `isOnIntegrationBranch` as `undefined` (not `false`) in that case, and the UI's "(not on )" sub-text only renders when we know we're on a different branch — not when we're on no branch at all. - d5cfa92: fix(dashboard): close 7 review findings on the extended-status hardening pass Follow-up to the prior fix commit; closes 7 more issues that an independent code review surfaced. **Settings inheritance regression (high)** — `SettingsModal.handleSave`'s non-model project branch lost the "only write if changed" gate when the prior commit added null-as-delete support. Result: every effective/inherited project key was being persisted as an explicit project override on every save, silently breaking inheritance across ~30+ keys. Restored the `value !== initialProjectValue` gate, matched against the model-lane branch's existing pattern. **Git Manager `Local vs origin` card showed misleading "Synced" in remote-only mode** — when `integrationTipSource === "remote-only"`, both `aheadOfOriginIntegration` / `behindOriginIntegration` are deliberately undefined (there's no local branch to compare), but the card's render fell through to `(ahead ?? 0) === 0 && (behind ?? 0) === 0 → "Synced"`. Now renders an explicit "no local tracking" sub-text in that case, with a separate `HEAD vs origin/` card surfacing a meaningful distance. **`isIndexStale` extended to multi-hop and gated to integration-branch worktrees** — - Walks up to 16 `refs/heads/` reflog entries so an A→B→C burst whose middle sync also missed is detected (the prior check only consulted `@{1}`). - Only fires when `isOnIntegrationBranch === true`. Previously, a feature-branch worktree whose HEAD happened to descend from `@{1}` (e.g. `git switch -c hotfix main@{N}`) would trip the stale-index warning despite being perfectly healthy. **`enumeration-failed` auto-sync events no longer dropped** — the new `(taskId, newSha)` join filter required both `worktreePath` and `newSha` on every auto-sync event, which discarded the merger's early-failure events that emit neither. Now: events with both fields use the per-advance pair-key (with macOS realpath canonicalization on both sides); events with neither use a task-id fallback so the diagnostic outcome still surfaces on the matching advance. **`aheadOfIntegration` no longer silently shifts semantics** — split into three distinct distance fields so consumers don't have to read `integrationTipSource` to know which comparison they got: - `aheadOfIntegration` / `behindIntegration` — HEAD vs **local** integration tip; undefined when only the remote tip exists. - `aheadOfIntegrationRemote` / `behindIntegrationRemote` — HEAD vs `origin/`; defined whenever the remote tracking ref exists. - `aheadOfOriginIntegration` / `behindOriginIntegration` — local integration tip vs `origin/`; defined only when both refs exist. **`currentBranch` failure no longer masks wrong-branch state** — `git branch --show-current` returns empty on detached HEAD (success) and throws on transient git errors (lock contention, timeout). The prior catch collapsed both into `currentBranch = ""` so the UI couldn't distinguish them. New `currentBranchDetectionFailed?: boolean` field on `GitStatus` lets the UI surface "branch detection unavailable" on a real failure rather than silently hiding the wrong-branch warning. - 916047c: feat(dashboard): Integration branch setting is now a dropdown of local branches with Custom… fallback Replaces the plain text input with a `