FN-8617 committed docs/dashboard-modal-inventory.md as the canonical
classification of all 45 dashboard modal surfaces (classes A-D) with
file:line evidence and FloatingWindow migration targets, but the docs
index was not updated. Add the Audit Reports entry so the doc is
discoverable from the docs hub.
Verified: orphan scan clean (only known intentional orphans remain);
pnpm --filter @runfusion/fusion test:docs-index passes (2/2).
Documents why self-healing force-removed a worktree a planning session was
using and parked the card branch-conflict-unrecoverable: planning gained a task
worktree but never took an active-session lease, so the reclaim sweep's liveness
guard had nothing to see, and a zero-commit branch classifies as
tip-already-merged by construction.
Captures the investigation's dead ends too — including reading maxConcurrent
from a multi-tenant config table without filtering by project_id, which produced
a confidently wrong root cause — and the three ways the first version of the fix
was itself wrong.
CONCEPTS.md: adds planning to the Active-session lease kinds (the entry had gone
stale), states the converse invariant that an unheld path reads as proof nothing
is running, and defines Top-level agent slot — the capacity concept whose
conflation with the worktree limit derailed the first hour of diagnosis.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code Review and Browser Verification now run with the card in `in-review`
instead of `in-progress`, so the board shows the card under review with the
running step as a badge (matching the Coding (Ideas) preset). Their paired
remediation nodes stay in `in-progress`, so a changes-requested verdict
visibly sends the card back to implementation.
The column move IS the badge switch: the dashboard badge was already
lane-gated on `column === "in-review"`. Applied to the shared stepwise
coding IR, so it is inherited by builtin:coding (the default),
builtin:stepwise-coding, builtin:brainstorming and builtin:coding-ideas;
builtin:legacy-coding keeps its historical placement.
Two consequences handled:
- Capacity: `in-review` has no `wip` trait, so the slot is released during
review and the remediation crossing back into `in-progress` can hit the
non-bypassable in-transaction capacity check. The column boundary now
PARKS the run on a `capacity-exhausted` rejection instead of failing it,
preserving the failed gate result and worktree so the next graph run
retries once a slot frees. Non-capacity rejections still propagate.
- Reopen clears: `applyReopenFieldClears` wiped `workflowStepResults` on
every in-review -> in-progress move, which the remediation crossing now
performs routinely. That destroyed the remediation input, made
`routeRetryableRemediationGraphFailureToPreMergeFix` and
`recoverFailedPreMergeWorkflowStep` silently no-op, and — worse — made
both `getTaskMergeBlocker` branches vacuously false, so a card could
return to `in-review` and be mergeable with its gate never re-run. Now
exempted for graph-owned in-review -> in-progress crossings only;
operator reopens, merge bounces and every -> todo/triage rebound still
clear, so the executor's documented bounce invariant is unchanged.
Adds regression coverage for both (there was previously none for the
reopen clear in either direction), and annotates the unreachable legacy
scheduler dispatch block rather than mirroring the fix into dead code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to 13a2b2a9d, from a multi-agent review of that commit. Three of its
claims did not hold.
1. fn_delegate_task bypassed the gate entirely (P0). It reaches the same
createAgentTask primitive, was registered unconditionally in both session
lanes, and validated only that the TARGET agent is non-ephemeral — never the
caller. Under Deny an ephemeral worker could enumerate agents and delegate
unlimited tasks. It is now withheld under Deny, and also under
upon_validation: delegation has no proposal channel, so leaving it available
would launder a create past the operator review that policy requires.
2. The widened dedupe window was capped at 5 minutes. The store query in
branch-and-pr-entities.ts carried its own independent `?? 60_000` /
`min(300_000, …)` pair, so widening only duplicate-guard.ts under-delivered
and made the new ceiling unreachable. Both sites now share
FINGERPRINT_WINDOW_DEFAULT_MS / FINGERPRINT_WINDOW_MAX_MS.
3. The pi-extension gate does not fire at all. pi's ExtensionContext carries no
agentId — the read is a speculative cast and only tests supply one, so every
real call short-circuits as a human caller. The fail-closed direction is kept
for the day an identity signal exists, but the limitation is now documented
instead of implied to be enforcement.
Also: the session prompt now states when creation is disabled and names
fn_task_log as the fallback (the base prompt still taught fn_task_create, which
is the same instruction/capability mismatch that fed the retry storm);
suppression emits an `agent:task-create-withheld` run-audit event; and the two
source-text ratchet tests are replaced with behavioral assertions on the tool
list the executor actually hands the model — verified to fail when the guard is
broken, which the string assertions did not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review of 2dbfe3d31 + 05b704dc6 surfaced real defects in both fixes:
- The unusable-worktree probe composed two helpers across an unnecessary
self-healing -> step-runner import edge, and the directory check added no
discriminating power over the `.git` probe. Replaced with one canonical
hasUsableWorktreeShape beside classifyTaskWorktree, which also applies the
repo-root gate (FN-6861) when a rootDir is available; both call sites pass one.
Its narrower guarantee vs the canonical classifier is now documented and
pinned by tests, including the de-registered shape it cannot see.
- REPLAN_PARK_STATUSES is derived from PLANNING_STAGE_STATUSES instead of
re-listed, so a new durable park status cannot be added to one set only.
- The preserve/clear decision no longer pretends to steer `worktree`: the rebound
is a reopen move, which clears it regardless. Documented, and the test now
asserts the durable row rather than only the updateTask argument.
- `branch` is cleared only when it is the re-derivable canonical fusion/<id>;
a non-canonical branch survives so a card's only commit pointer is not dropped.
- The recovery log named the recorded worktree even when the session had targeted
an AI-merge clean room. It now names the refused path and says whether the
recorded worktree was gone too.
- Added task:auto-recover-worktree-session-metadata so the decision is legible to
agents, not only in human log prose.
- isTaskStillInPlanningStage's parameter type now includes the execution stamps
its implementation reads.
- Test hygiene: real-fs fixtures wrapped in try/finally; changeset dev note
corrected; FN-8361 asserted at the discovery surface, not only in the guard
table.
Also captures the shared bug class in docs/solutions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add the `autoUpdateAndRestart` global setting (default off, Settings ->
General next to Release channel). When enabled, the dashboard host installs
available updates on the selected channel by itself and requests the
supervised in-place restart. Supervised hosts only: without a parent to
respawn, installing would leave a running process whose code no longer
matches its own install.
Fix two ways the restart affordance could silently do nothing:
- The supervisor now stamps FUSION_SUPERVISOR_PID and supervision is only
counted when that pid is the real parent. FUSION_RESTART_SUPERVISED is
inherited by every process Fusion spawns, so `fn dashboard` launched from
an agent terminal skipped its own supervisor while still advertising
restart support -- a restart request then killed it for good.
- Settings and the update banner probe /system/info on mount and treat
capability as advisory: the button always issues the request and shows the
server's actual refusal instead of sitting disabled after a failed probe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Global settings are already split three ways -- values in settings.json, the
revision journal in postgres, and globalMaxConcurrent/defaultProjectId in
central tables -- so the recurring "finish the cutover" proposal keeps getting
re-litigated from scratch.
Write down the two hard constraints (startup-factory reads
embeddedPostgresMaxConnections to start postgres; createFusionAuthStorage is
synchronous and host-agnostic), the recovery argument, and the one real
motivation for a partial move (multi-node policy consistency), plus the
machine-tier vs operator-policy-tier rule for placing new keys.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An operator can hold a raw Anthropic API key and a Claude subscription OAuth
login at once, and the raw key always won silently. A stale or revoked saved
key therefore shadowed a working subscription and failed every direct Anthropic
call with 401 invalid x-api-key, while both Settings cards still read Active.
Add the global anthropicAuthPreference setting ("api-key" default, preserving
the historical precedence, or "subscription"), read in resolveAnthropicRuntimeApiKey
straight from ~/.fusion/settings.json so it applies without a restart and needs
no settings plumbing through createFusionAuthStorage. Neither value removes a
source: with one credential configured, resolution reaches it either way.
Settings -> Authentication now names the credential in use on the two Anthropic
cards and renders the control, but only when both are actually connected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the hidden long-press/right-click Start menu on Quick Add's Save with a
visible Start button in the action row, rendered only for workflows whose first
visible lane is a hold column (or Coding (Ideas)). Eligibility, the workflow
snapshot, the create-time column override, and the hold-first follow-up move are
unchanged; ineligible workflows render no Start chip or shell.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review follow-up to 907e8d03e (ce-code-review, 8 personas):
- Ctrl/Cmd+V with no clipboard API (or after a denied read) now returns
false WITHOUT preventDefault: xterm skips key handling and the browser's
default paste fires xterm's helper-textarea listener once. Returning true
made non-mac xterm inject \x16 and cancel the native paste (verified
against xterm 5.5.0 _keyDown).
- A denied clipboard read sets a sticky ref so later pastes use the native
path instead of being preventDefaulted into zero delivery.
- Custom-path delivery goes through terminal.paste() to restore bracketed
paste and newline normalization.
- 'Start terminal' surfaces createTab failures in the error banner and
disables while a create is in flight (no duplicate PTY sessions).
- normalizeActiveTab() extracted so storage-read and server-validation
share one all-inactive tie-break; failure-path regression test added.
- Rewrote docs/solutions/ui-bugs/xterm-async-font-remeasure-paste-dedupe.md
to the current paste contract (was prescribing the pre-#1902 behavior).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## Problem
With `pushAfterMerge` enabled (and `mergeStrategy` other than
`pull-request`), if `origin/<integration-branch>` advances externally
between the local squash-merge and the push, the divergence path opens a
clean-room `git pull --rebase` and an AI agent resolves and stages the
conflicts — but the flow could end there: no `git rebase --continue`, no
push, and no surfaced error.
Because finalize runs *before* the push, the task is already `done`, so
a reviewed, approved merge is silently left container-only, and every
subsequent merge on the project stalls the same way. Separately, an
abort mid-push (`MergeAbortedError`) was swallowed with only a
process-log warning — no task-log entry, no run-audit event.
## Change
- **Deterministic regression coverage** for the conflicting-divergence
path (real-git fixture) proving the rebase runs to completion and the
push lands (refs converge), plus abort/termination scenarios.
- **Recovery-branch safety net:** before the clean-room rebase starts,
the pre-rebase local squash is force-pushed to a per-task remote branch
`fusion/<task-id>-stranded`, so approved content is never container-only
— even across process death or abort. Deleted after a successful target
push; retained on failure/abort as the recovery source.
- **Never-silent outcomes:** every non-pushed outcome (failure or abort)
writes a durable task-log entry and a `push:origin` run-audit event. The
audit contract now documents `push:origin` as polymorphic (dashboard
Smart Push vs. automated post-merge push) and enumerates the automated
path's outcomes, including the new `"aborted"` shutdown case.
- **Cleanup hardening:** `isRebaseInProgress` now probes Git's
worktree-specific `rebase-merge`/`rebase-apply` state directories
(async, timeout-guarded) so a completed rebase can't receive a spurious
second `--continue`; unfinished rebases are cleaned up.
Out of scope by design: withholding the "merge confirmed" state until
the push succeeds — the `FNXC:MergePush` invariant ("a push problem can
never park or roll back a landed merge") is deliberate; the recovery
branch + surfacing satisfy the data-preservation intent without breaking
it.
## Files
`packages/engine/src/merger-ai.ts`, `packages/engine/src/merger.ts`,
`packages/engine/src/run-audit.ts`, new/updated tests under
`packages/engine/src/__tests__/`, `docs/settings-reference.md`,
`docs/dashboard-guide.md`, `AGENTS.md`, and a labeled changeset.
## Validation
`tsc --noEmit` clean; engine divergence + merger suites pass (41 tests);
rebased onto current `main` with no conflicts.
---
_Developed with Claude Code, under human supervision and review._
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Prevented approved post-merge pushes from becoming stranded when the
remote diverges by using a recovery-branch workflow and safer cleanup.
* Improved behavior and reporting when pushes are aborted or fail after
merge, including clearer non-fatal status and audit outcomes.
* **Documentation**
* Expanded push-after-merge and dashboard Smart Push documentation with
recovery-branch and `push:origin`/`push:recovery-branch` outcome
semantics.
* **Tests**
* Added end-to-end regression tests for divergent/conflicting AI
push-after-merge flows, including abort and worktree cleanup
verification.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Victor Cano <victortroz@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Keep all selected GitHub issue actions touch-safe and visible on a single mobile row.
- Group detail action controls separately from the comment composer and make their mobile tracks shrinkable.
- Add responsive browser-smoke coverage at 320px, 390px, and 412px plus modal structure tests.
- Document the mobile behavior and add a patch changeset.
Files changed:
.changeset/fn-8548-mobile-github-import-actions.md | 7 +
docs/dashboard-guide.md | 8 +-
.../dashboard/app/components/GitHubImportModal.css | 42 +++++-
.../dashboard/app/components/GitHubImportModal.tsx | 80 ++++++------
.../__tests__/GitHubImportModal.test.tsx | 50 ++++++++
packages/dashboard/app/styles.css | 11 +-
.../dashboard/scripts/browser-layout-smoke.mjs | 142 ++++++++++++++++++++-
7 files changed, 290 insertions(+), 50 deletions(-)
Fusion-Task-Id: FN-8548
Fusion-Task-Lineage: 8fab6a1d-0ca9-4246-9707-e1fc84ca58e5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The runner stage installed the application at /project, which was also the
documented bind-mount point — mounting a host project there shadowed the CLI
and the container exited with MODULE_NOT_FOUND on packages/cli/dist/bin.js.
- Install the app under /app and run the entrypoint by absolute path.
- Reserve /workspace (empty in the image, container workdir) as the project
mount point.
- Update docs/docker.md: mount at /workspace, and document that embedded
Postgres/global state lives in /home/node/.fusion with a named-volume
example so persistence actually captures it.
Verified: image builds; `docker run -v host:/workspace` boots, embedded
Postgres initializes, /api/health returns ok.
Fixes#2414
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## Summary
Gives dashboard integrators (plugin views, embedded panels, theming
tools) a supported way to match the dashboard's look and to layer
overlay UI correctly — instead of scraping computed styles and guessing
z-index values. This implements the CSS-token bridge slice of
`docs/proposals/2026-07-01-dashboard-theme-plugin-system.md`.
Two additions, both inert unless used:
1. **Documented theme-token contract.** A "Theme tokens" section in
`docs/dashboard-guide.md` (referenced from `docs/PLUGIN_AUTHORING.md`)
declares the stable set of CSS custom properties — colors, surfaces,
status colors — that integrators may read. Tokens resolve to raw color
strings (e.g. `#161b22`) in every theme, including the newer ones. A
sync test (`theme-token-contract-docs.test.ts`) parses the doc's token
table and asserts each documented token has a real definition in the
dashboard CSS, so the contract cannot silently drift from the code.
2. **Overlay layering surface.** Overlay-style UI (palettes, pickers,
floating panels) currently has no supported way to sit above the
floating-window stack — the effective max z-index is runtime state
inside `floatingWindowStack.ts`. This PR exposes it:
- `--fusion-max-z` on `:root` — kept in sync by `floatingWindowStack`
(written at module load and after every `nextFloatingZ()` claim), so it
always reflects the true top of the dashboard-managed stack. Boot/floor
value is `11001`, chosen to clear the highest statically-declared layer
(the body-portaled model-combobox dropdown at `z-index: 11000`).
- `#plugin-overlay-root` — an empty, `pointer-events: none` sibling of
`#root` stacked at `calc(var(--fusion-max-z) + 1)`. React never renders
into it, so it is hydration-safe; integrators portal into it and
re-enable pointer events on their own elements.
- The layer bands (base UI / floating windows / toasts / dropdown /
overlay root) are documented in `styles.css` and the guide, and a guard
test (`dashboard-max-z-guard.test.ts`) scans the structural + component
CSS and fails if any static `z-index` is ever introduced above the floor
— keeping the contract honest as the codebase evolves.
## Behavior
No visual or behavioral change for existing users: `floatingWindowStack`
still returns the same values from `nextFloatingZ()`; the overlay root
is empty and click-through; tokens were already defined — this only
documents and guards them.
## Tests
- `theme-token-contract-docs.test.ts` — docs ↔ CSS sync
(non-tautological: anchored matching against real definitions).
- `floatingWindowStack.max-z.test.ts` — `--fusion-max-z` boot value and
live tracking as the stack claims z-indexes.
- `dashboard-max-z-guard.test.ts` — no static dashboard z-index above
the floor (decorative `public/theme-data.css` INT_MAX scanline overlay
deliberately excluded; it's non-interactive grain, documented in the
test).
- Changeset included (`minor`, `category: feature`). Typecheck clean.
## Open question for maintainers
The token is named `--fusion-max-z`. The existing scale uses `--z-*`
names (`--z-dropdown`, `--z-modal`) on a lower band — happy to rename to
`--z-max` / `--z-plugin-overlay` or anything that fits your convention;
the name is the only bikeshed here, the sync mechanism is independent of
it.
## AI assistance disclosure
Parts of this change were authored with AI assistance (Anthropic's
Claude); the commit carries a `Co-authored-by` trailer accordingly.
Everything was human-reviewed before submission, and the test suite and
typecheck were run locally against the current `main`.
If squash-merging with a rewritten message, please keep the attribution:
```
Co-authored-by: Claude <noreply@anthropic.com>
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added stable dashboard theme tokens for consistent plugin/integration
styling.
- Introduced a dedicated plugin overlay mount point with click-through
defaults and an overlay stacking ceiling.
- Overlay z-index now stays in sync with floating window layering
automatically.
- **Documentation**
- Added an explicit stable “theme token contract” and “overlay layering
contract,” including interaction and z-index usage rules and deprecation
expectations.
- **Bug Fixes**
- Improved reliability of plugin overlay stacking so overlay content
renders above intended dashboard layers.
- **Tests**
- Added guards validating CSS z-index ceilings and enforcing the
documented theme token contract.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: Claude <noreply@anthropic.com>