f6010ef558fac8774788ec418240cfbaf133fe70
135 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9dbc98f1b3 |
Audit: every sync workflow-IR read answers for the DEFAULT workflow (not a PG-only problem) (#2653)
Docs only. This came out of a #2593 review thread that reported the problem as PostgreSQL-specific. **It is unconditional**, and it has consequences well outside the guard I was fixing — including one that looks like a live production break for custom workflows. ## The chain, each link checkable 1. `TaskStore.getTaskWorkflowSelection(taskId)` delegates straight to `getTaskWorkflowSelectionImpl` — **no mode branch** (`store.ts:2545`). 2. `getTaskWorkflowSelectionImpl` **returns `undefined` unconditionally** (`workflow-definitions.ts:505-512`). Its own comment: *"sync selection reader is incomplete-PG; use getTaskWorkflowSelectionAsync."* A PG-cutover stub that never got finished. 3. So `resolveTaskWorkflowIrSyncImpl` always takes its `if (!workflowId)` branch and returns `resolveDefaultWorkflowIr()`. Its `isBuiltinWorkflowId` and `SELECT ir FROM workflows` branches are **unreachable in production**. `resolveTaskWorkflowIrSync` is typed `WorkflowIr`, non-optional — so callers cannot detect the substitution. There is no `undefined` to check and the IR that arrives looks valid. **Why tests don't catch it:** test stores stub `getTaskWorkflowSelection` with a real selection, so the reader works under test and substitutes only in production. Any test written against a stubbed store proves the caller's logic and never the reader's behavior. ## Consequences, severity descending 1. **Custom fields appear to be rejected on custom workflows.** `resolveTaskCustomFieldDefsSyncImpl` returns `ir.fields` — the DEFAULT workflow's. `task-update.ts:128-136` validates against them, and its own comment states the outcome: *"a write against a workflow with no fields (the default) is rejected with a typed CustomFieldRejectionError."* 2. **Per-workflow capacity pools collapse** — `resolveEffectiveWorkflowIdSyncImpl` reads the same selection, so every task resolves to `resolveCapacityPoolId(undefined)`. 3. **Plugin transition hooks re-run against the wrong IR** (`lifecycle-ops.ts:1052`, crash recovery). 4. **Terminal-node detection degrades** to `nodeId === "end"` (`branch-and-pr-entities.ts:578`). 5. **A U7 guard was inert** — fixed in #2593. Its fail-closed arm was `workflowIr ? … : true`, dead code against a non-optional return. **#1 and #2 are REASONED FROM SOURCE, NOT OBSERVED.** I did not execute those paths, and I am labelling them that way in the doc rather than reporting them as confirmed. No test in `packages/core` covers `CustomFieldRejectionError` or `resolveTaskCustomFieldDefsSync` — consistent with the gap, but absence of a test is not proof of a break. **Reproduce before fixing.** I would rather hand you a labelled hypothesis than a confident claim I did not verify. ## Why this matters for the fleet, specifically The census work replaces column literals with trait lookups. A conversion that resolves its traits through a **sync** reader produces a guard that reads the DEFAULT workflow's traits for every task — plausible, wrong, and invisible. **It converts a visible literal into a hidden bug**, and the ratchet counts it as progress. Suggested addition to the fleet brief: conversions must resolve through `resolveWorkflowIrForTaskWithProvenance` and branch on `source`; `resolveTaskWorkflowIrSync` is never acceptable in a converted guard. ## Not fixed here Each consequence needs its sync call path made async — a real slice per site, not an end-of-turn edit. #2593 fixed only the one that was mine. Census unchanged (781 / triage 5); this PR adds and converts no guards. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added an architecture-pattern finding documenting a workflow-reading limitation that can cause synchronous reads to use the default workflow. * Described resulting effects on custom workflow updates, crash recovery, capacity-pool handling, and terminal-node detection. * Documented testing gaps and guidance to avoid synchronous task workflow reads. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2771408bba |
ci: enforce the lifecycle-column ratchet — it has never actually run (#2654)
**The ratchet was advisory.** `scripts/lifecycle-column-census.mjs` existed only as `pnpm census:lifecycle-columns` — without `--strict` — and **no workflow invoked it**. Nothing has ever compared the tree to the baseline. Every "the baseline ratchet holds them" assumption in this program rested on a check that does not run. That explains both classes of hole: **1. Three PRs lowered counts without re-recording,** leaving allowances the deleted guards could return through while every check stayed green. I've tightened them across #2593 and earlier PRs, but nothing stops the next one. **2. #2621 GREW the count while its own title claimed "count 0 → 0".** It added `column === "triage"` and `column === "todo"` at `register-task-workflow-routes.ts:2681`, taking that file to **23 against an allowance of 22**. It landed unchallenged. This is the failure mode the ratchet exists to prevent, and it happened *inside this program*, in a PR that asserted the opposite. ## The change Adds `check:lifecycle-columns` (the census with `--strict`) to the `pr-checks.yml` lint job, next to `check:changesets` and `check:routes-modular` — the established pattern. **~1.8s over ~1950 files**, so this is not a slow-test addition. ## Proven to fail, in both directions A guard that reports success without checking anything is worse than no guard, so: | injected defect | result | |---|---| | `const __probe = (c: string) => c === "triage"` added to `moves.ts` | `count ROSE — moves.ts: 39 -> 40`, exit 1 | | run against main's current baseline | exit 1 on `mission-feature-sync.ts: allows 5, tree has 0` | Both reverted; exit 0 restored. Note the second row: **this check is RED on main right now**, which is the point. ## Merge order **Stacked on #2593**, which carries the `DELIBERATE-LITERAL` marker for the #2621 site (a v1 IR declares no roles, so no trait can answer that question) plus the baseline re-record. Standalone on main this PR is red — correctly. **Merge #2593 first**, then this. I stacked rather than duplicating those two edits because I already caused one conflict today by appending related content from two branches, and #2651 merged a correction ahead of the section it corrected. Same-content edits in two PRs is the same mistake. ## Census Unchanged by this PR: **776 total, triage 5, reviewed 16** — it adds no guards and converts none. It only makes the numbers enforceable. ## For the fleet This should land before the 776-guard fleet launches. The brief says "the baseline ratchet must shrink by exactly the converted count" — until now nothing verified that claim, so a batch worker could report a shrink that did not happen, or grow the count while converting, and CI would agree. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5481c27729 |
docs(solutions): finding 6 — read the implementation before claiming its output is wrong (#2649)
Completes `proving-a-code-path-actually-runs.md` (merged as #2642) with the rule its own author broke three times while writing it. **Docs only.** ## Why this belongs in that document rather than a new one Findings 1-5 are about proving **your own** claim: does this path run, can this test fail, is this negative result observable. Finding 6 is the mirror image — the claims we make against **other people's** work — and it is the same underlying error pointed outward. Splitting them would let a reader take the first five as "be rigorous about my code" and miss that the identical discipline applies when reviewing someone else's. ## The three cases, all mine, all in one day | What I claimed | What was actually true | |---|---| | The census undercounts triage guards, 13 vs 10 | `summarize()` counts `byColumnId` only for `kind === "column"`. My patched counter summed `role`, `status` and `deliberate` too. The three "missing" ones were exactly the ones it classifies correctly — and I reported this against the instrument the program had just adopted as authoritative. | | `resolvePlannerLanesForTask` silently disables two recovery paths for legacy cards — escalated across four messages | The file's own header had already reasoned it through and documented why that answer is correct. And `TaskStore` implements `getTaskWorkflowSelectionAsync`, which the resolver prefers — so real projects never take the path my `{ getTask }`-only probe forced. | | `executor.ts` is clean of triage guards | A receiver-specific grep missed three under `from` and `originColumn`. Same error one step earlier: trusting a reconstruction of the thing instead of the thing. | Every one was: reconstruct behaviour from outside → compare to actual output → find a difference → report a defect, **without reading the implementation.** ## The rules it adds - Read the implementation and its header comment before reporting anything as wrong. On this codebase the reasoning is usually already written down, and the FNXC note frequently answers the exact objection — twice today it answered mine verbatim. - **A fixture is not a measurement of production.** When a probe and the real system disagree, suspect the probe: ask what it had to stub, and whether production ever supplies that shape. - Retract precisely and immediately. A false defect report against shared infrastructure costs more than the bug would have — it sends people to verify something already correct, and spends the credibility needed for the next report that is real. Also updates the count in the intro (five → six) and adds an `applies_when` entry so the doc surfaces for "about to report a tool as defective", which is when it is needed and not when someone is already debugging. `pnpm lint` clean. No changeset — internal documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0c07584d51 |
U11 fallout: disprove the coding-ideas column collapse, and correct a U11 note that recorded the merge backwards (#2651)
Two findings, no behavior change. Both are about **recorded reasoning that was wrong** — the kind that sends the next person the wrong way. ## 1. The coding-ideas column collapse does not work (IR change reverted) I implemented it — deleted `ideas`, moved its `intake`/`autoTriage: false` onto Planning, repointed the `start` anchor, updated the IR suites to the merged shape (they went green, 44/44). Then the wider suites failed and showed why it cannot work. **The manual gate IS the column boundary.** `replan-target.ts` names the discriminator in its own comment: *"The real discriminator is which lane the triage service SCANS, which depends on the intake column's `autoTriage` config."* So `ideas` is unscanned, `todo` is scanned, and "promote" means moving the card from one into the other. Merge them and one column must be both: | if… | consequence | |---|---| | `autoTriage: false` wins | never scanned → nothing is ever planned → the capacity hold releases an **unplanned** card into `in-progress`, violating FN-7648 | | scanning wins | `autoTriage: false` is meaningless → the manual gate is gone → the preset duplicates the default Coding workflow | **8 tests fail, and they are not fixtures** — they encode the promotion flow itself, e.g. `store-create-intake-column.test.ts` › *"promotes an Ideas-parked task to todo without planning it (still bootstrap-stub PROMPT.md)"*. Rewriting them would have meant inventing what "promote" means with no destination column, which is how a broken flow gets blessed by a green suite. **What it would actually take:** a promoted flag the triage scan reads, so one column can hold both "not yet promoted" and "being planned". That is a new lifecycle signal, not a column merge — the same shape as the deferred `needs-replan` follow-up. Happy to scope it. **I also corrected my own earlier checklist** in this doc, which said to delete the now-dead `isUnplannedStartCreate` arm. Wrong: `autoTriage` is a general trait field (`builtin-traits.ts`), so any custom workflow can declare a manual intake with `intake !== hold`. The arm is dead only for this preset. ## 2. `replan-target.ts` recorded the U11 merge backwards The note claimed U11 deletes `todo` and keeps `triage`. It is the reverse — Shape B kept the id `todo` and deleted `triage`, precisely so the ~120 `column === "todo"` guards kept their meaning and no data migration shipped. The default lineage now declares `todo, in-progress, in-review, done, archived`. The lookups are correct today, but **for the opposite reason to the one recorded**: the default lineage falls *through* the `triage` lookup and lands on `todo`, its merged planning column. `triage` still matches the workflows that genuinely declare it (Lead generation, PR review). Also flagged without changing (it would be a behavior change): the `return "triage"` fallbacks on the no-match and throw paths name a column the default lineage no longer declares, so a workflow with neither `triage` nor `todo` gets a nonexistent target. ## Census **Unchanged: 781 total, triage 5.** This PR adds no guards and converts none — `workflowHasColumn(ir, "triage")` is a call argument, not a comparison, so it is outside what the census counts either way. ## Verification 41/41 engine replan-target suites (including the existing `replan-target-merged-planning-column` suite that covers the corrected behavior) · engine typecheck clean · the reverted IR restores the tree to main's content for those three files, verified by `git checkout --`. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7ab6506c0f |
docs(solutions): proving a code path actually runs — the five ways U8 shipped code that never executed (#2642)
Durable write-up of U8's verification findings. **Docs only — no code change, no CI risk beyond lint.** These currently exist only in PR bodies, which nobody greps. `docs/solutions/` is where this project keeps exactly this kind of thing, and every one of the five will recur: the handler-pair shape and the resolved-vs-guessed fork both have more call sites than U8 touched. ## The five 1. **Two prompt-node handlers exist; only one runs.** `createDefaultNodeHandlers` prefers the primitives handler whenever `deps.primitives` is set, and `executeWorkflowGraph` always sets it — so every seam entry in `createAuthoritativeWorkflowSeams` is unreachable for prompt nodes. A lifecycle announcement sat there through two PRs. It type-checked and its unit tests passed, because a seam-level test calls the seam object directly and therefore always can. 2. **A negative instrumentation result is worthless without a control.** No output from an instrumented seam is only evidence once you have shown writes from that module are visible under the harness. One `process.stderr.write` at module load separates "never ran" from "output swallowed" — opposite conclusions. 3. **Source-string ratchets prove syntax, not behavior.** Three were torn down in review. The sharpest guarded a never-executed-code bug with a source search, reproducing the bug one level up; measured, the behavioural version fails an inverted dispatch and the textual one passes it. Includes the sub-rules paid for the hard way: use the AST not regex (a brace in a string truncated an extraction to 13 lines and every count read a *passing* zero), guard the guard, anchor by index rather than a character window. 4. **A green test on first try, on a path with no prior coverage, is a warning.** Two conversions were reverted in one day because their tests passed with the change reverted. Negative assertions succeed trivially when the method returns early — `recoverCompletedTask` has seven guards before the converted line, and the fixture has to satisfy all of them. 5. **A named workflow selection is not a resolved one.** Provenance cannot be inferred from the returned value, because a fallback IR and a valid id-less IR are structurally identical — the resolver that knows has to report it. This is the fork every remaining lifecycle-column conversion hits. ## Why this rather than another conversion Everything left in my area is now owned and further along than I could take it: `executor.ts` → #2628 (which solved the `recoverCompletedTask` fixture I could not), `self-healing.ts` → #2560 (independently hit all three traps I catalogued), the dashboard cluster → #2625/#2626/#2636. Duplicating that would be motion, not progress. Turning findings that cost real cycles into something greppable is the useful thing I can still add. `pnpm lint` clean. No changeset — internal documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a best-practices guide for verifying that workflow code paths actually execute. * Covers reliable behavioral assertions, instrumentation controls, regression-proof tests, source validation, and detection of fallback behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6d10683dbd |
docs(solutions): store fakes that lie — six fixture defects that each looked like a production bug (#2534)
Six consecutive slices of U7 produced **six test-fixture defects, and every one first presented as a bug in the code under test.** Not one was real. Each cost 15–60 minutes debugging the wrong file. **Two would have shipped a false green** — a test passing while asserting nothing — if the failure had happened to look plausible rather than implausible. This is not a story about carelessness. Every one of these fakes was modelled on an existing fixture in this repo, and the repo's fixtures are inconsistent about exactly the things that matter. ## The catalogue | # | Defect | How it presented | Real cause | |---|---|---|---| | 1 | `moveTaskIf` ignores its predicate | Test passed; in-txn guard untested and indistinguishable from absent | Fake never invoked the callback | | 2 | `updateTaskAtomic: vi.fn()` never invokes its callback | *Every* finalize bailed before the branch under test | Success is derived from whether the callback ran | | 3 | Harness default parameter swallows the input | "Task vanished" case became a duplicate of the control | `harness(undefined)` triggers the default | | 4 | `logEntry: vi.fn()` returns `undefined` | Sweep appeared to match only one column | `.catch` on a non-promise throws, aborting the loop after item one | | 5 | Harness lets `poll()` reach the real `specifyTask` | **exit 1 with every test green** | Real agent path threw *asynchronously*, after assertions passed | | 6 | `updateTask: vi.fn()` returns `undefined` | Branch "did not run" | Same as #4 | **4 and 6 are the same shape, found a week apart, because nothing prevented the second.** That is the argument for writing this down. ## The three rules 1. **Every store method a fake exposes returns what the real one returns** — overwhelmingly a promise. Production writes `await store.m(...).catch(h)` as a fail-soft idiom; `.catch` on `undefined` throws a `TypeError` that unwinds into a broad *"never let housekeeping break the poll"* handler and vanishes. Symptom is never "your fake is wrong" — it is *"the loop only processed the first item"*. 2. **A fake handed a predicate or callback must invoke it.** Ignoring it makes the guarded and unguarded implementations *indistinguishable*, so a test named for the guard cannot detect the guard's removal. Includes the `onLockedRead` hook, without which an in-transaction recheck stays untestable even once the predicate is invoked. 3. **Stub the agent-dispatch boundary.** `poll()` ends in "start an agent", which in a unit test throws *after* the test resolved — `17 passed`, exit code 1, which on CI reads as infrastructure noise. > Never accept a non-zero exit on a green run. It is the only signal that something escaped your assertions entirely. ## Also covered - **How to spot a fixture defect fast** — the tell is *failing for the wrong reason*. Three concrete checks before you open the production file. - **Why differential tests earn their keep** even when they feel redundant: the default-vocabulary half doubles as a fixture self-check, because it asserts behavior that is by definition already shipping. On this program, "both halves failed" was the signal that found three of the six. - **The connection to guards that cannot fire** — six of those on this program too, including a ratchet I wrote that matched only a double-quoted literal (#2527). Same discipline either way: *prove the check fails on the thing it claims to catch before trusting that it passes.* Including the warning that one ratchet injection silently failed to apply, leaving a green run that would have "proven" the ratchet worked. ## The concrete next step, stated plainly A shared `createTaskStoreFake({ tasks, workflowIr })` with promise-resolving, callback-invoking defaults would remove this whole class in one small PR. **It is not built here** because it is cross-unit and needs adopters — building it inside U7 and hoping others find it is how conventions die. The doc says: if you are about to hand-roll a seventh store fake, build the helper instead and link it. Docs-only; no changeset (AGENTS.md excludes internal docs). 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added guidance on six store-fake defect patterns that can resemble production bugs during testing. * Documented best practices for creating reliable store fakes, including promise handling, callback invocation, and async dispatch isolation. * Added diagnostic techniques for distinguishing fixture issues from genuine application defects. * Included guidance for validating production guards and links to related documentation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9a11e0b136 |
U2b reproduction: the live move path accepts the column U11 deleted (characterized, not patched) (#2601)
Found while proving U11's caveat 2. **Characterization plus guard-rails
— no production change, deliberately.**
## The defect
A default-workflow card in Planning can be moved **into `triage`** — a
column its workflow no longer declares — re-creating exactly the
stranded state `reconcileUndeclaredTaskColumns` exists to repair.
Measured on a fresh store:
```
experimentalFeatures.workflowColumns null ← no production writer
createTask(...) column = "todo"
moveTask("todo" → "triage") ACCEPTED
moveTask("todo" → "bogus-column") REJECTED: "Valid targets: in-progress, triage, archived"
```
The second rejection is the tell. Validation is real — but it is the
**legacy `VALID_TRANSITIONS`** table talking, and that table does not
know the card's workflow. Its `todo` row still lists `triage`.
## Why the workflow-aware check does not run
`moves.ts` gates its adjacency block — including
`workflowHasColumn(workflowIr, toColumn)` — on
`isWorkflowColumnsCompatibilityFlagEnabled`, which reads the raw
`experimentalFeatures.workflowColumns` key. Nothing writes it, so the
block is dead on the path every real project takes.
**Corollary, already reported:** U11's undeclared-source escape hatch in
`resolveAllowedColumns` also does not run in production. It was added
with #2515 so a stranded card would have a legal move instead of `Valid
targets: none`; on the live path that rescue comes from the legacy table
instead. Mutation-verified — stubbing the hatch back to `[]` leaves the
operator-move test green.
## Why I did not fix it
PR #2499 un-gated the capacity check and **explicitly scoped validation
out**:
> SCOPE, deliberately narrow: only the CAPACITY check is un-gated.
`workflowIr` stays flag-gated so transition VALIDATION keeps its current
behavior — the inline path's bare-Error/"Valid targets:" contract is
unchanged, and none of the Phase A2 divergences are flipped here.
That is a considered decision by the owner of this function, and several
suites pin the contract it protects. Overriding it from outside would
flip an error shape I do not own.
**What has changed since that decision is U11:** the legacy table now
offers a target the default workflow does not declare, which it never
did before. That is new input to the scoping call, not licence to ignore
it — so this lands as a reproduction for U2b rather than a patch.
U2b's branch (`feature/workflow-move-path-convergence`) is stale — HEAD
predates several merged PRs, clean tree — so nothing is being raced.
## What ships
The defect is **characterized, not asserted-as-correct**: the test pins
today's behaviour so it is visible and measurable, and an `it.todo`
states the intended behaviour. Writing it as a passing "refuses" test
would have required the fix; writing it as a failing test would redden
CI; asserting the current behaviour as *correct* would be a lie.
Characterization plus `it.todo` is the honest third option.
Four guard-rails pin what a fix must **not** break:
- every declared lifecycle move (`todo → in-progress → in-review →
done`)
- archiving
- a `recoveryRehome` deliberately reaching an undeclared column — the
path that rescues already-stranded cards, and the one a careless fix
would break
- a premise test asserting the compatibility flag really is unset, so
the suite fails loudly if that ever changes rather than silently testing
a different code path
## Exposure
Narrow but real. U10 already fixed the dashboard move menu to offer only
workflow-declared targets, so the board does not present this. The
**write path** does — REST API, CLI, plugins, any stale client — which
is why the guard belongs in `moves.ts` rather than only in the UI.
5 passed + 1 todo; lint clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Added coverage for task moves involving workflow-declared and
undeclared columns.
* Documented a known issue where tasks can currently be moved into the
deleted `triage` column.
* Preserved valid moves, archiving, and recovery re-homing behavior.
* **Documentation**
* Added reproduction steps, affected move paths, and guardrails for
addressing the issue.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
30e0a8f291 |
U11 P0 audit: no hard stall in the recovery block — and one obvious fix is wrong (#2570)
Docs only. Answers the P0 question per site: **does it still fire, what silently stops happening, is there a backup?** ## Headline: no hard stall The alarming reading — *"the orphaned-planning-status sweeps stop finding default cards, so a card whose planner died sits with `status:"planning"` forever, invisible to discovery"* — **does not hold.** `triage.ts`'s `sweepStalePlanningStatuses` is the **periodic primary** for that repair and already tests `column !== "triage" && column !== "todo"`. It covers the merged column. The two self-healing sweeps perform the same repair and are **redundant nets**, not the sole rescue. That is the difference between a P0 and a cleanup, and it is only visible by reading the **backup** path rather than the broken guard. Recorded so nobody re-derives the panic. ## Self-healing block, by blast radius | site | fires? | what stops | backup | verdict | |---|---|---|---|---| | `:12106`, `:12427` | no | clearing a stale `planning` status | `triage.sweepStalePlanningStatuses` | redundant net lost — **cleanup** | | `:2961/2981/3016` `recoverAdvancedTriageTasks` | no | re-homing a card with a worktree + durable IR pin to its **pinned** resume column | hold-release still releases it on capacity (real spec ⇒ `isUnplannedForExecution` false) | **degraded, not stuck** — fix first | | `:12254` | no | a bounded priority nudge | none needed; the doc says nudge, not rescue | **low** | | `:12151`, `:9151` | **yes** | — | already OR-paired | **safe** | **Second-order trap at `:3016`.** It skips when `resumeColumn === "triage"`, guarding against resuming a card into the column it already occupies. Post-merge the pinned column is `todo`, which is **not** skipped — so pairing the literal at `:2961` *without* also pairing `:3016` produces a `todo → todo` move. **Repair the three together.** ## Two sites in the ownership split are already handled - **`usage-limit-detector.ts:126`** (assigned to u8) — already fixed in **PR #2567**. Real breakage: the planning lane stopped being recognised, so a card being planned was neither parked when its provider hit a usage limit nor resumed when it recovered. - **`spec-staleness.ts:95`** (assigned to u7) — already proven safe as-is, merged with #2515. **Its obvious fix is wrong.** I tried `|| task.column === "todo"` and it turned an existing test red: it breaks the parked-preserved-progress path. ## The generalisation, which is the most useful thing here **On the merged column, `todo` answers two different questions.** After the merge `todo` is both the planner column *and* the capacity-hold column. So any site that used `triage` to mean *"is being planned"* **cannot simply be paired with `todo`**, because `todo` also means *"is parked waiting for capacity"*. Those sites need **status or a trait**, not a wider literal. That is precisely the mistake a bulk conversion makes, and `spec-staleness.ts` is the worked example: the guard was already asking status, and widening the column would have destroyed the distinction. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
67904f8a2c |
U11: merge Todo into Planning on the default lineage (+ the migration mechanism, and a measured safety audit that cuts the work list 32%) (#2515)
**Merges Todo into Planning on the operator's real default workflow.** Held from merge pending the `triage` literal audit below — see *Gating*. ## The board change `builtin:coding` → `BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR` → clones `BUILTIN_STEPWISE_CODING_WORKFLOW_IR`. That IR now declares **five** columns, and `plan`, `plan-review`, `plan-replan` and `start` all live in the merged Planning column: ``` columns: todo="Planning", in-progress, in-review, done, archived start -> todo plan -> todo plan-review -> todo plan-replan -> todo parse -> in-progress (first implementation node) ``` The id stays `todo`, the display name becomes "Planning". That is the cheaper half: `todo` was already the hold column, so every trait lookup, task row, stored selection and the 121 `column === "todo"` guards keep their meaning, and **no stored row needs re-homing**. Promoting `triage` instead would have produced the same board while making those guards workflow-*dependent* — live for Coding (Ideas), silently dead for Coding. `builtin:legacy-coding` keeps its six-column shape, per the operator's decision. It exists to be the old thing. ## Entry contract, before and after each IR edit | | result | |---|---| | before the default-lineage edit | **15 passed** | | after the edit | **13 passed, 2 failed** | | after reading both | **15 passed** | Neither failure was routed around. One was a genuine expectation change (two planning entry points became one); the other was my own `mergeTodoIntoPlanning` helper throwing *"source IR is not the split-column shape this merge transforms"* — because production **is** the merged shape now. I **deleted** the helper rather than making it tolerant: a transform that has silently become a no-op asserts nothing. ## The safety argument, proven not asserted Entering at `start` is exactly what dragged cards backward in the three earlier reverted attempts. `merged-planning-start-node-no-move.test.ts` proves against the **real** boundary controller and **real** default IR that entering `start` performs no move (`moveTask` is never *called*), reaches no hold→wip capacity seam, and **still moves on a genuine crossing** so the no-op is same-column rather than a disabled boundary. Removing the controller's same-column short-circuit turns exactly the two no-move tests red. ## The migration mechanism A card can outlive its column. `resolveAllowedColumns` derives targets from graph adjacency, and an undeclared source has none — so it returned `[]` and **every** move was rejected with "Valid targets: none", including the one that would rescue the card. An undeclared source now resolves to the workflow's rebound target. Escape hatch, not relaxation: declared columns are untouched, and it offers the rebound target *only*, so a stranded card gets back **into** the lifecycle rather than a free jump past review. ## A real regression this surfaced `isDefaultWorkflowColumns` matched the legacy **six** ids as a set. The merged default declares five, so the match stopped firing and the default board fell through to neighbor-only adjacency, which **drops legal moves and invents an illegal one**: | edge | effect | |---|---| | `in-progress → done` | **dropped** — the mission-validation cross edge | | `in-review → todo` | **dropped** — review work back to planning | | `todo/done → archived` | **dropped** — the FN-4892 direct-archival edges | | `done → in-review` | **invented** — a backward edge no rule allows | Adjacency now derives from lifecycle **roles**. The load-bearing assertion: the legacy six still reproduce `VALID_TRANSITIONS` **verbatim**. Applied only when a workflow declares the full role set, so custom boards keep neighbor adjacency. ## Failure accounting (core package, vs a 49-failure baseline) | stage | failed | new | |---|---:|---:| | after the merge | 65 | 18 | | after the escape hatch | 52 | 5 | | after role-derived adjacency | 53 | 4 | The 4 remaining are 3 `builtin-workflows` expectations encoding the pre-merge shape and 1 create-intake expectation naming `triage` on `builtin:coding`. Two `schema-applier` and two `workflow-reconciliation-production-shape` failures appeared in intermediate runs and are **not mine** — both files pass in isolation (75/75 and 7/7). I re-ran each before attributing them, which is why the earlier "priority" flag on the reconciliation pair was withdrawn. Gate: **309/309**. Lint clean. ## Gating: the `triage` audit (`docs/solutions/architecture-patterns/u11-triage-literal-safety-audit.md`) Program tracking cited **58** `triage` comparisons. Measured with the same pattern: | | count | |---|---:| | raw comparisons | 87 | | inside comments | 1 | | **not a lifecycle column at all** | **15** | | column comparisons | 71 | | OR-paired with `"todo"` in the same expression | 32 | | **exclusive `triage` — the real work list** | **39** | **15 do not compare a column.** `role === "triage"`, `surface === "triage"`, `sessionPurpose === "triage"`, `entry.agent === "triage"` name the planning **agent**. Converting them would be actively wrong, and the failure — a planning agent that can't resolve its prompt template — would look nothing like a column bug. **One site changes an operator-visible affordance**, which is why per-site review beat a sweep: `TaskCard.tsx:1927` — `taskColumnFlags?.intake === true && task.column !== "triage"`. The literal is a **narrowing**, not a match. After the merge a Planning card has `intake === true` and `column === "todo"`, so the narrowing stops applying and **Start begins rendering on default Planning cards where it previously did not.** A sweep would have "converted" the literal and shipped the new affordance silently. These guards do not go **dead**, they go **workflow-dependent** — `triage` stays live for legacy-coding, Ideas, every linear built-in and any user workflow (R11) — which is harder to detect than dead. Work list and ownership are in the audit doc. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3c5d07ab01 |
docs: workflow-owned lifecycle program plan + column-placement contract (#2463)
Docs only — no code. Companion to #2462. ## `docs/solutions/architecture-patterns/workflow-node-column-placement-and-graph-entry-contract.md` Why a workflow node's `column` is a lifecycle contract rather than a display choice: it decides **who can drive the node**, **whether the card holds a WIP slot**, and **whether anything can move it onward**. Contents: - The graph **entry contract** (`resolveColumnResumeNode`, shipped in #2462) with the resume table. - The **plan-in-place chain** — triage → finalize → continuation seed → drain → resume → capacity suspend → release — annotated with the check each link performs. Notably `todo`, not `triage`: an intake column has no releaser, so a card parked there waits for a human. - Why the pre-release gate must be narrow (column match **and** enablement). - The measured failure table from three reverted placement attempts. - Why removing a column is a lifecycle-vocabulary refactor, not a workflow edit: **82 guards** that silently stop matching, **43 writes** to a column that no longer exists, **59 dashboard literals**. A guard that never fires doesn't fail a test — it disables a recovery path. ## `docs/plans/2026-07-26-001-refactor-workflow-owned-lifecycle-plan.md` The program that finishes the job, in four movements: 1. Resolve lifecycle columns from the workflow instead of ~207 string literals. 2. Move every lane — planning, execution, review, merge — behind graph nodes; lane services keep substrate only (storage, leases, timers, supervision, capacity, recovery, audit). 3. A **post-commit event seam**: transitions commit transactionally, *then* emit; subscribers react and may enqueue durable work items, but no subscriber performs a transition. Enforced by test — dropping every subscriber must change no lifecycle outcome. 4. Only then merge Todo into a single Planning column. Phased so each phase lands green independently, with the IR change deliberately **last** (KTD-7). Changing the workflow first makes the suite green over dead guards — that's how the earlier attempts hid their own breakage. The merge lane **adopts** the existing design in `docs/plans/2026-06-09-003-refactor-workflow-owned-merge-full-migration-slices-plan.md` (slices S02–S08, still `draft-stack-handoff`) rather than authoring a competing one, with a note to re-validate against current `main` since it was drafted seven weeks ago. Scale is stated honestly: ~48k lines across the four lane services, with the executor unit explicitly landing across several commits rather than one sweep. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a516c8b409 |
docs(FN-8600): capture the live-planning-worktree reclaim incident
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> |
||
|
|
0c85613313 |
fix(engine): address code-review findings on the planner/worktree recovery fixes
Review of |
||
|
|
3c3acbf769 |
docs(settings): record why global settings are not all in postgres
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> |
||
|
|
faeb491ea4 |
fix(dashboard): harden terminal paste contract and manual-start button per review
Review follow-up to
|
||
|
|
51fc34f585 |
FN-8465: align skill toggles with canonical paths
Align session skill filtering with Skills-view path identities. - Match session inclusions, exclusions, and diagnostics by skills-relative body path. - Ignore stale flat toggle keys for categorized skills and cover the display/session invariant. - Document the behavior and add a patch changeset. Files changed: .../fn-8465-legacy-skill-toggle-path-match.md | 7 ++ docs/dashboard-guide.md | 2 +- .../legacy-flat-skill-toggle-session-divergence.md | 41 ++++++++ .../engine/src/__tests__/skill-resolver.test.ts | 73 ++++++++++++++ packages/engine/src/skill-resolver.ts | 105 ++++++++++++++------- 5 files changed, 191 insertions(+), 37 deletions(-) Fusion-Task-Id: FN-8465 Fusion-Task-Lineage: 042fc7f4-08a3-42f4-9a6f-52f066d585e8 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
ac52438554 |
FN-8192: prevent mobile confirmation ghost-click dismissal
Keep mobile task-delete confirmations open through delayed compatibility clicks. - Gate backdrop dismissal until the opening gesture settles. - Cover deliberate backdrop dismissal and mobile ghost-click behavior. - Document the regression and add a patch changeset. Files changed: .changeset/fn-8192-mobile-confirm-ghost-click.md | 7 +++ .../confirm-dialog-mobile-ghost-click-dismiss.md | 53 ++++++++++++++++++++++ .../dashboard/app/components/ConfirmDialog.tsx | 23 +++++++++- .../components/__tests__/ConfirmDialog.test.tsx | 34 ++++++++++++-- .../app/hooks/__tests__/useConfirm.test.ts | 32 ++++++++++++- 5 files changed, 143 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-8192 Fusion-Task-Lineage: ea08a6c0-0f2e-4ec3-8ad7-23cdc4a5d7f1 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
bc7dfe4bbf |
FN-8178: fix task-card context menu autofocus dismissal
Prevent portaled task-card menus from closing when autofocus creates a scroll event. - Focus the first menu action with preventScroll while preserving keyboard access. - Cover all card menu entry points and intentional dismissal paths. - Document the interaction lifecycle fix and publish a patch changeset. Files changed: .changeset/fn-8178-context-menu-flash-dismiss.md | 7 +++ docs/dashboard-guide.md | 2 +- .../task-card-context-menu-flash-dismiss-layers.md | 35 +++++++++++ .../dashboard/app/components/TaskContextMenu.tsx | 9 ++- .../app/components/__tests__/TaskCard.test.tsx | 69 ++++++++++++++++++++++ 5 files changed, 120 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-8178 Fusion-Task-Lineage: 2eb94ffd-e922-497d-8904-1aea8bf760b1 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
d0ce7829c0 |
FN-7953: fix mobile OAuth code submit taps
Submit Anthropic OAuth manual codes on the first mobile tap instead of requiring keyboard dismissal first. - Add a reusable touch action gesture hook that handles touch/pointer activation before synthetic clicks. - Wire the OAuth manual code Submit button to invoke submission on the first touch while preventing duplicate click handling. - Cover the mobile double-tap regression and document the UI bug pattern for future fixes. Files changed: .../oauth-manual-code-mobile-double-tap-submit.md | 60 +++++++++++ .../app/components/OAuthManualCodeForm.tsx | 31 +++++- .../__tests__/OAuthManualCodeForm.test.tsx | 110 +++++++++++++++++++++ .../hooks/__tests__/useTouchActionGesture.test.ts | 110 +++++++++++++++++++++ .../dashboard/app/hooks/useTouchActionGesture.ts | 89 +++++++++++++++++ 5 files changed, 399 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7953 Fusion-Task-Lineage: d387cdbd-25a7-4b7d-add6-27a1ded5cbea Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
725ce45c5d |
FN-7799: fix false-negative Git repository detection blocking task execution
Replace the boolean isGitRepository() check with a tri-state Git detection so environmental git failures (dubious ownership, missing git binary, timeouts) are no longer misreported as "not a Git repository", which previously blocked all task execution in valid repos and survived engine restarts. - Add detectGitRepository() in worktree-pool.ts returning repo / not-repo / error (with reason: dubious-ownership, git-missing, timeout, unknown), classified from git's stderr; bound the git rev-parse call with a 10s timeout and maxBuffer; keep isGitRepository() as a backward-compatible wrapper - Route the executor dispatch preflight guard through detectGitRepository(): only emit the original "not a Git repository / run git init" fatal on a positive not-repo verdict; on error, throw a distinct accurate error naming the real git failure, including the safe.directory remedy for dubious ownership - Route the in-process runtime startup warning through the same tri-state detection so it only warns "not a Git repository" on a positive not-repo verdict - Add a regression test locking extractWorktreeConflictInfo() to NOT misclassify a dubious-ownership git worktree add failure as not-git-repo - Add targeted tests across worktree-pool, executor-worktree, and in-process-runtime test suites covering repo/not-repo/dubious-ownership/git-missing/timeout classifications on Windows OneDrive-style and POSIX paths - Add changeset and a docs/solutions/logic-errors write-up of the false-negative root cause and fix Files changed: .changeset/fn-7799-git-detection-false-negative.md | 7 +++ .../logic-errors/git-detection-false-not-repo.md | 54 ++++++++++++++++ .../engine/src/__tests__/executor-worktree.test.ts | 61 +++++++++++++++++++ .../engine/src/__tests__/worktree-pool.test.ts | 71 +++++++++++++++++++--- packages/engine/src/executor.ts | 38 +++++++++--- .../runtimes/__tests__/in-process-runtime.test.ts | 53 ++++++++++++++-- packages/engine/src/runtimes/in-process-runtime.ts | 16 ++++- packages/engine/src/worktree-pool.ts | 66 ++++++++++++++++++-- 8 files changed, 334 insertions(+), 32 deletions(-) Fusion-Task-Id: FN-7799 Fusion-Task-Lineage: 25a84283-bf47-472b-8a98-a10bf7e494de Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
6a13ad175e |
FN-7732: remove dangling release-authorization UI/i18n/docs scaffolding
Narrative: The triage release-authorization gate itself was already removed in b5b0458; this cleans up the leftover scaffolding it left behind — an unemitted activity type, a dead TaskCard badge/label/CSS, orphaned i18n keys across all 6 locales, and a stale solutions doc — so the codebase no longer references a gate that no longer exists. - Drop the unused `task:release-authorization-required` ActivityEventType and its label/rendering in ActivityFeed.tsx and ActivityLogModal.tsx - Remove the dead `isReleaseAuthorizationHold` badge logic and `.awaiting-release-authorization` CSS class from TaskCard.tsx/TaskCard.css - Simplify TaskDetailModal.tsx comments/logic now that legacy release-authorization holds render as ordinary manual plan-approval holds - Delete orphaned i18n keys `tasks.awaitingReleaseAuthorization` and `taskDetail.plan.releaseAuthorizationHold` across en/es/fr/ko/zh-CN/zh-TW locales and resources.d.ts - Delete the stale docs/solutions/architecture-patterns/release-triage-requires-user-authorization.md doc - Update docs/workflow-steps.md and docs/settings-reference.md to describe the gate as removed (superseded by FN-7732) instead of documenting still-active behavior - Add changeset for @runfusion/fusion (patch/internal) Files changed: .changeset/fn-7732-remove-release-authorization-block.md | 7 +++++ docs/settings-reference.md | 2 +- docs/solutions/architecture-patterns/release-triage-requires-user-authorization.md | 33 ---------------------- docs/workflow-steps.md | 6 ++-- packages/core/src/types.ts | 8 ++++-- packages/dashboard/app/components/ActivityFeed.tsx | 5 ---- packages/dashboard/app/components/ActivityLogModal.tsx | 6 ---- packages/dashboard/app/components/TaskCard.css | 11 -------- packages/dashboard/app/components/TaskCard.tsx | 13 +++------ packages/dashboard/app/components/TaskDetailModal.tsx | 14 +++------ packages/i18n/locales/en/app.json | 3 -- packages/i18n/locales/es/app.json | 5 +--- packages/i18n/locales/fr/app.json | 5 +--- packages/i18n/locales/ko/app.json | 5 +--- packages/i18n/locales/zh-CN/app.json | 5 +--- packages/i18n/locales/zh-TW/app.json | 5 +--- packages/i18n/src/resources.d.ts | 3 -- 17 files changed, 30 insertions(+), 106 deletions(-) Fusion-Task-Id: FN-7732 Fusion-Task-Lineage: d4137bd8-9056-4062-9f2a-c6f5d47295f4 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
a24b0fac1a |
FN-7721: cap heartbeat worktree-acquisition retries and record exhaustion failures
Bounds durable-agent heartbeat worktree acquisition to a fixed retry count instead of requeuing to todo indefinitely across heartbeat cycles. - Add MAX_HEARTBEAT_WORKTREE_ACQUISITION_RETRIES (3) in agent-heartbeat.ts, reusing Task.recoveryRetryCount as a cross-heartbeat counter (no schema migration) - On cap exhaustion, terminally mark the task status:"failed" with an explanatory error, log the entry, and reopen to todo with preserveStatus so the failed status isn't wiped by reopen-to-todo semantics - Add onTaskAcquisitionExhausted callback wired in in-process-runtime.ts to CentralCore.recordTaskCompletion(taskId, false) so exhausted acquisitions count toward totalTasksFailed - Add regression tests in agent-heartbeat-worktree.test.ts and in-process-runtime.test.ts covering the retry cap and completion recording - Add changeset (patch) and a docs/solutions/logic-errors writeup documenting the investigation and other worktree-collision sub-gaps found not to reproduce on HEAD Files changed: .changeset/fn-7721-worktree-heartbeat-retry-cap.md | 7 ++ docs/solutions/logic-errors/heartbeat-worktree-acquisition-unbounded-requeue.md | 84 ++++++++++++++++++++++ packages/engine/src/__tests__/agent-heartbeat-worktree.test.ts | 58 +++++++++++++++ packages/engine/src/__tests__/in-process-runtime.test.ts | 11 +++ packages/engine/src/agent-heartbeat.ts | 72 ++++++++++++++++++- packages/engine/src/runtimes/in-process-runtime.ts | 12 ++++ 6 files changed, 242 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-7721 Fusion-Task-Lineage: caad671c-f360-4c1c-8aaa-5b48fca5a55b Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
0755fc5747 |
fix(FN-7693): mobile terminal blank — exempt xterm measurement from * { max-width: 100% }
Real root cause of the blank mobile terminal (the FN-7692 remeasure guard did not
fix it and is reverted here). styles.css has a mobile-only reset
`@media (max-width: 768px) { * { max-width: 100% } }` to prevent horizontal
overflow. That universal selector also matches xterm's hidden character-measurement
subtree (`.xterm-helpers` / `.xterm-char-measure-element`). That subtree's containing
block (`.xterm-helpers`) is a 0x0 absolutely-positioned box, so `max-width: 100%`
resolves to `max-width: 0` and hard-caps xterm's character-cell measurement at 0.
FitAddon.fit() then proposes 0 columns/rows and `.xterm-screen` (plus the WebGL
canvas) collapses to 0x0 — the prompt streams in and is written into xterm's row DOM
but paints into a zero-size box, so the terminal is blank. Mobile-only, which is why
desktop always rendered fine.
Reproduced live via mobile emulation: `.xterm-char-measure-element` measured 0 while
an identical monospace span in the same container measured ~295px; `max-width: none`
on the measure element restored ~295px, and reopening the terminal with the exemption
active rendered the prompt with `.xterm-screen` sized 369x760. No amount of
remeasure/refit can fix this — the CSS re-caps the measurement to 0 every time — so
the FN-7692 CharSizeService guard is removed.
- Exempt `.xterm-helpers` / `.xterm-char-measure-element` from the mobile max-width
reset in styles.css (covers both TerminalModal and SessionTerminal)
- Revert the ineffective FN-7692 remeasure guard and its tests
- Update changeset (patch) and the docs/solutions write-up to the real root cause
Note: root cause + fix validated in the automation browser via mobile emulation
(393px, iPhone UA, forced touch), not a physical device.
Fusion-Task-Id: FN-7693
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
b7b1b71cae |
fix(FN-7692): recover blank mobile terminal when xterm screen collapses to 0x0
The mobile terminal rendered blank even though the WebSocket was Connected and the shell prompt had already streamed in. Root cause (reproduced live): on the mobile fullscreen layout xterm's CharSizeService can measure a 0-width character cell, so FitAddon.fit() proposes 0 columns/rows and .xterm-screen (plus the WebGL canvas) collapses to 0x0 — prompt bytes arrive and are written into xterm's row DOM but paint into a zero-size box. Renderer-independent and mobile-layout- specific; not fixed by resize/font-size re-fits because prior guards only validate the container width and font load, never the resulting measured screen/cell width. Add guardAgainstCollapsedTerminalScreen (app/utils/terminalPreferences.ts) and arm it from both terminal surfaces (TerminalModal + SessionTerminal) right after their initial fit. While the container has a width but .xterm-screen does not, it forces a genuine DOM-strategy remeasure (forceTerminalFontRemeasure) + fit, re-driven by a ResizeObserver until the screen has a real width. It waits (does not give up) while the container is not yet measurable, is bounded so it never spins, and is disposed on every re-init/close path. Recurrence of FN-7620/FN-7686. - Add isTerminalScreenCollapsed + guardAgainstCollapsedTerminalScreen with tests - Wire + dispose the guard across all xterm (re)init/close paths in both surfaces - Add changeset (patch) and a docs/solutions write-up Note: reproduced via mobile emulation (393px, iPhone UA, forced touch), not a physical device; the guard is the structural fix — confirm on a real device. Fusion-Task-Id: FN-7692 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
07507f5264 |
FN-7688: add slow login-shell profile latency hint and docs
Investigated whether --login in TerminalService first-prompt latency is a meaningful contributor and added a one-time diagnostic hint plus documentation of findings. - Add SLOW_LOGIN_PROFILE_HINT_MS (2000ms) threshold and one-time, non-blocking console.info hint in createSession()'s PTY onData handler when a login shell is slow to produce first output - Track spawnStartedAt and loginProfileHintLogged per session, and whether the succeeding spawn attempt used --login, without altering spawn args, timeouts, or the retry-without-login fallback - Add regression tests covering the slow-login-profile hint behavior in terminal-service.test.ts - Document the investigation and findings in docs/solutions/developer-experience/login-shell-profile-latency.md and link it from docs/dashboard-guide.md - Add a patch changeset for @runfusion/fusion describing the new server-log hint Files changed: .changeset/fn-7688-login-shell-profile-latency.md | 7 ++ docs/dashboard-guide.md | 17 +++ .../login-shell-profile-latency.md | 79 ++++++++++++++ .../src/__tests__/terminal-service.test.ts | 121 +++++++++++++++++++++ packages/dashboard/src/terminal-service.ts | 57 ++++++++++ 5 files changed, 281 insertions(+) Fusion-Task-Id: FN-7688 Fusion-Task-Lineage: 08d5dd47-ea9f-4973-9f0e-a8d5fdeae111 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
a832b7979f |
FN-7686: skip redundant session-list round trip on fresh terminal load
Speed up initial terminal load by short-circuiting the no-op server session list call when there are no persisted local tabs to validate. - useTerminalSessions: when readTabsFromStorage returns zero tabs, skip the listTerminalSessions HTTP call entirely and mark bootstrap ready immediately, unblocking auto-create/WebSocket connect instead of serializing behind a provably-discarded round trip - Reload-with-persisted-tabs path is unchanged and still awaits the list call since its result is decision-relevant there - Add regression tests covering the fresh-load fast path and the persisted-tabs path - Add changeset (patch) and a docs/solutions write-up of the bootstrap-list-serialized-before-auto-create issue Files changed: .changeset/fn-7686-slow-terminal-initial-load.md | 7 ++ ...bootstrap-list-serialized-before-auto-create.md | 87 ++++++++++++++++++++++ .../hooks/__tests__/useTerminalSessions.test.ts | 73 ++++++++++++++++++ .../dashboard/app/hooks/useTerminalSessions.ts | 23 +++++- 4 files changed, 189 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7686 Fusion-Task-Lineage: 9c708329-6362-4c2e-967f-aea12849c47c Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
ca7c987ab5 |
FN-7621: fix mobile terminal shortcut bar horizontal scroll defeated by ancestor touch-action lock
Root-caused and fixed the third recurrence of the mobile terminal shortcut bar not scrolling horizontally: styles.css's mobile lockdown resets touch-action to pan-y across ancestors, and touch-action's used value is the intersection of the touched element's and every ancestor's value, so the leaf .terminal-shortcut-panel's pan-x was silently defeated even though it was already correct. - Opt the terminal overlay and modal ancestors (.modal-overlay.terminal-modal-overlay, .modal.terminal-modal--mobile, plain-media-query mobile modal, and the shortcut/status footer) into touch-action: pan-x pan-y so descendant leaf touch-action values can take effect - Add FNXC:Terminal comments documenting the ancestor-intersection root cause and recurrence history (FN-7550/FN-7560) - Add a documented solution note under docs/solutions/ui-bugs/ for the ancestor-intersection touch-action pattern - Add regression tests asserting the modal/overlay/footer ancestors carry the pan-x pan-y opt-in - Add a changeset for the fix Files changed: .../fn-7621-mobile-terminal-shortcut-scroll.md | 7 ++ ...on-ancestor-intersection-defeats-leaf-scroll.md | 57 +++++++++++ .../dashboard/app/components/TerminalModal.css | 38 ++++++++ .../components/__tests__/TerminalModal.test.tsx | 106 +++++++++++++++++++++ 4 files changed, 208 insertions(+) Fusion-Task-Id: FN-7621 Fusion-Task-Lineage: 771fd79e-e193-43b0-908b-0e8fe2fc2c70 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
8d73b18c35 |
FN-7620: fix mobile terminal rendering blank via ResizeObserver recovery
Fixes the mobile dashboard terminal sometimes rendering completely blank on open by having TerminalModal recover from a zero/collapsed container box. - TerminalModal now attaches a persistent ResizeObserver directly on the xterm container, mirroring SessionTerminal's existing pattern - When the container reports a zero/collapsed box on the first post-open fit, it now re-fits once the real box settles instead of staying stuck at FitAddon's degenerate 2x1-cell floor - Added regression tests covering TerminalModal and SessionTerminal zero-geometry recovery - Documented the root cause and fix in docs/solutions/ui-bugs/mobile-terminal-blank-render-zero-geometry-container.md - Added a patch changeset for @runfusion/fusion Files changed: .changeset/fn-7620-mobile-terminal-blank-render.md | 7 + docs/solutions/ui-bugs/mobile-terminal-blank-render-zero-geometry-container.md | 112 +++++++ packages/dashboard/app/components/TerminalModal.tsx | 49 +++ packages/dashboard/app/components/__tests__/SessionTerminal.test.tsx | 66 ++++ packages/dashboard/app/components/__tests__/TerminalModal.test.tsx | 358 +++++++++++++++++++++ 5 files changed, 592 insertions(+) Fusion-Task-Id: FN-7620 Fusion-Task-Lineage: 103f5b17-9a6e-4e9a-ab61-65ccb2203a8d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
e347062e1f |
FN-7603: force xterm DOM-based char measurement to fix mobile terminal spacing
Fixes recurrence #5 of mobile terminal inter-character spacing by unifying xterm's cell-width measurement pipeline with WidthCache's DOM-based glyph measurement, validated against real xterm instead of the jsdom mock. - Add withDomBasedTerminalCharacterMeasurement() in terminalPreferences.ts: transiently hides window.OffscreenCanvas during terminal.open() so CharSizeService's constructor throws and self-selects its own DOM-based fallback strategy, unifying dimensions.css.cell.width with WidthCache.get('W') measurement - Wire withDomBasedTerminalCharacterMeasurement() around terminal.open() calls in SessionTerminal.tsx and TerminalModal.tsx - Add FNXC:Terminal comments documenting the Canvas-vs-DOM measurement divergence root cause, grounded in the installed @xterm/xterm@5.5.0 source - Add docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md recurrence #5 section - Expand TerminalModal.test.tsx coverage for the new measurement-forcing behavior - Add changeset fn-7603-mobile-terminal-spacing.md (patch, fix) Files changed: .changeset/fn-7603-mobile-terminal-spacing.md | 7 + docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md | 101 ++++++ packages/dashboard/app/components/SessionTerminal.tsx | 16 +- packages/dashboard/app/components/TerminalModal.tsx | 16 +- packages/dashboard/app/components/__tests__/TerminalModal.test.tsx | 363 ++++++++++++++++++++- packages/dashboard/app/utils/terminalPreferences.ts | 63 ++++ 6 files changed, 554 insertions(+), 12 deletions(-) Fusion-Task-Id: FN-7603 Fusion-Task-Lineage: 6c7d980f-953e-4fa9-908e-b24125904cbe Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
7c0be5311a |
FN-7567: re-bake xterm letter-spacing after fit() to fix mobile terminal spacing recurrence
Fixes recurrence #4 of the mobile terminal excess character-spacing bug: SessionTerminal and TerminalModal now force a second genuine xterm font remeasure AFTER fitAddon.fit() settles the post-fit column count, since handleResize() never re-bakes DomRenderer's letter-spacing compensation itself. - SessionTerminal.tsx: call forceTerminalFontRemeasure() again after fit()/sendResizeMessage() in the resize handler, re-baking spacing against the settled (post-fit) column count instead of the stale pre-fit one. - TerminalModal.tsx: same second forceTerminalFontRemeasure() call after fitAddon.fit()/sendResize() in its resize handling path. - Expanded SessionTerminal.test.tsx and TerminalModal.test.tsx coverage to assert the post-fit remeasure occurs. - Added docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md documenting recurrence #4 root cause (DomRenderer._setDefaultSpacing() never recomputes from handleResize()). - Added changeset fn-7567-mobile-terminal-spacing.md (patch, category fix). Files changed: .changeset/fn-7567-mobile-terminal-spacing.md | 7 + docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md | 89 ++++++ packages/dashboard/app/components/SessionTerminal.tsx | 23 ++ packages/dashboard/app/components/TerminalModal.tsx | 43 ++- packages/dashboard/app/components/__tests__/SessionTerminal.test.tsx | 127 +++++++- packages/dashboard/app/components/__tests__/TerminalModal.test.tsx | 334 ++++++++++++++++++++- 6 files changed, 619 insertions(+), 4 deletions(-) Fusion-Task-Id: FN-7567 Fusion-Task-Lineage: 5da20522-82d3-4c4d-9008-db71bc5b4d75 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
9d4a45b0f9 |
FN-7561: fix mobile terminal excess inter-character spacing after font settle
Root-cause fix for xterm text rendering with wide inter-character gaps on mobile after fonts settle post-load. - Added forceTerminalFontRemeasure() in terminalPreferences.ts to work around xterm's OptionsService setter being a no-op when reassigning an already-current fontFamily/fontSize - Applied the remeasure helper at every post-waitForTerminalFontMetrics() settle site in TerminalModal.tsx and SessionTerminal.tsx - Added regression tests covering the remeasure invariant across SessionTerminal, TerminalModal, and terminalPreferences - Documented root cause and fix in docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md - Added changeset for @runfusion/fusion (patch) Files changed: .changeset/fn-7561-mobile-terminal-spacing.md | 7 + docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md | 120 ++++++++++ packages/dashboard/app/components/SessionTerminal.tsx | 11 +- packages/dashboard/app/components/TerminalModal.tsx | 11 +- packages/dashboard/app/components/__tests__/SessionTerminal.test.tsx | 108 ++++++++- packages/dashboard/app/components/__tests__/TerminalModal.test.tsx | 257 ++++++++++++++++++++- packages/dashboard/app/utils/__tests__/terminalPreferences.test.ts | 55 +++++ packages/dashboard/app/utils/terminalPreferences.ts | 38 +++ 8 files changed, 601 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-7561 Fusion-Task-Lineage: ad9da396-abf4-46ea-8c60-f2ed40fa4b01 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
7300bf54ce |
FN-7460: disable WebKit text scaling in terminals
Disable WebKit terminal text scaling so iPhone Safari preserves exact xterm cell metrics. - Set terminal xterm host subtrees to disable text-size adjustment instead of pinning it at 100%. - Update terminal CSS contract coverage for modal and session terminal surfaces. - Document the real-iPhone recurrence and add a patch changeset for the published CLI package. Files changed: .changeset/fn-7460-ios-terminal-spacing.md | 7 +++++++ .../ui-bugs/xterm-async-font-remeasure-paste-dedupe.md | 7 ++++--- packages/dashboard/app/__tests__/terminal-input.test.ts | 14 +++++++------- packages/dashboard/app/components/SessionTerminal.css | 7 +++++-- packages/dashboard/app/components/TerminalModal.css | 9 ++++++--- .../app/components/__tests__/TerminalModal.test.tsx | 7 +++++++ 6 files changed, 36 insertions(+), 15 deletions(-) Fusion-Task-Id: FN-7460 Fusion-Task-Lineage: 00542017-457b-40b8-9139-084218bc3533 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
8d7abb8c33 |
FN-7447: fix Android terminal mobile viewport sizing
Fix Android keyboard-open terminal layout by sizing mobile shells from the visual viewport. - Track visualViewport width in the terminal modal and mobile keyboard detection. - Apply keyboard-constrained mobile terminal sizing even when CSS media queries see a wider layout viewport. - Cover Android visualViewport width behavior with modal, session terminal, and hook regressions. - Document the fix pattern and add a patch changeset for the published Fusion package. Files changed: .../fn-7447-android-mobile-terminal-spacing.md | 7 ++ ...ndroid-mobile-terminal-visual-viewport-width.md | 26 ++++++++ .../dashboard/app/components/TerminalModal.css | 21 +++++- .../dashboard/app/components/TerminalModal.tsx | 8 +++ .../__tests__/SessionTerminal.mobile.test.tsx | 40 +++++++++++- .../components/__tests__/TerminalModal.test.tsx | 76 ++++++++++++++++++++++ .../app/hooks/__tests__/useMobileKeyboard.test.ts | 38 +++++++++++ packages/dashboard/app/hooks/useMobileKeyboard.ts | 10 ++- 8 files changed, 222 insertions(+), 4 deletions(-) Fusion-Task-Id: FN-7447 Fusion-Task-Lineage: c01faf0f-842e-4632-bf8f-ae54070c3bf8 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
18b07b5b7d |
FN-7388: Fix folded Android terminal mobile sizing
Fix Android foldable terminal surfaces so initial folded panes use mobile viewport geometry.\n\n- Use touch visualViewport dimensions when classifying terminal mobile mode.\n- Apply mobile TerminalModal shell styles outside media queries for folded Android panes.\n- Cover folded Android viewport behavior in terminal and viewport-mode tests.\n- Document the folded viewport baseline invariant and add a patch changeset.\n\nFiles changed:\n .changeset/fn-7388-android-folded-terminal-spacing.md | 7 +++\n docs/solutions/ui-bugs/mobile-terminal-folded-viewport-baseline.md | 8 ++-\n packages/dashboard/app/components/SessionTerminal.tsx | 18 +++++-\n packages/dashboard/app/components/TerminalModal.css | 37 +++++++++++\n packages/dashboard/app/components/TerminalModal.tsx | 31 ++++++++-\n packages/dashboard/app/components/__tests__/SessionTerminal.mobile.test.tsx | 31 +++++++++\n packages/dashboard/app/components/__tests__/TerminalModal.test.tsx | 73 ++++++++++++++++++++++\n packages/dashboard/app/hooks/__tests__/useViewportMode.test.ts | 29 ++++++++-\n packages/dashboard/app/hooks/useViewportMode.ts | 26 +++++++-\n 9 files changed, 251 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-7388 Fusion-Task-Lineage: cdf2ef0d-be1f-4414-9113-17ffebdecde5 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
8f65186bdc |
FN-7289: fix folded-phone terminal keyboard spacing
Preserve mobile terminal geometry when folded phones open with the keyboard already visible. - Use layout viewport height when computing keyboard overlap so focused keyboard-open folded postures keep a valid baseline. - Allow keyboard-closed width/posture changes at short folded heights to refresh the terminal viewport baseline. - Add regression coverage for TerminalModal and SessionTerminal folded-phone keyboard spacing and document the recurrence. Files changed: .../fn-7289-mobile-terminal-folded-keyboard.md | 7 ++ .../mobile-terminal-folded-viewport-baseline.md | 11 +- .../dashboard/app/components/TerminalModal.tsx | 20 ++-- .../__tests__/SessionTerminal.mobile.test.tsx | 30 +++++ .../components/__tests__/TerminalModal.test.tsx | 127 +++++++++++++++++++++ 5 files changed, 183 insertions(+), 12 deletions(-) Fusion-Task-Id: FN-7289 Fusion-Task-Lineage: 82134835-ca72-4796-89da-c8d071efc481 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
ac87b1e8e4 |
FN-7281: fix folded terminal viewport baselines
Keep mobile terminals sized against the settled folded viewport before keyboard overlap is applied. - Re-baseline terminal keyboard metrics when folded devices settle to a new closed-posture width. - Avoid replacing the baseline from focused keyboard-open samples that would erase real keyboard overlap. - Cover TerminalModal and SessionTerminal mobile keyboard spacing behavior with regression tests. - Document the folded-phone terminal spacing fix and add a patch changeset. Files changed: .changeset/fn-7281-mobile-terminal-spacing.md | 7 ++ .../mobile-terminal-folded-viewport-baseline.md | 56 +++++++++++++++ .../dashboard/app/components/TerminalModal.tsx | 57 ++++++++++++++-- .../__tests__/SessionTerminal.mobile.test.tsx | 39 +++++++++++ .../components/__tests__/TerminalModal.test.tsx | 79 ++++++++++++++++++++++ .../app/hooks/__tests__/useMobileKeyboard.test.ts | 53 ++++++++++++++- packages/dashboard/app/hooks/useMobileKeyboard.ts | 31 +++++++-- 7 files changed, 309 insertions(+), 13 deletions(-) Fusion-Task-Id: FN-7281 Fusion-Task-Lineage: 90ab7398-c583-4312-8cf6-0ef7d6328c8d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
c17d745665 |
FN-7109: notify operators when CLI agents await input
Notify external providers when terminal-backed CLI agents pause for tool permission or user input. - Add a dedicated cli-agent-awaiting-input notification event to settings, schema defaults, dashboard options, and provider tests. - Dispatch CLI-agent waiting-on-input notifications from the in-process runtime with task context and prompt-scoped dedupe keys. - Document the operator behavior and add a published package changeset for the new notification surface. Files changed: .changeset/fn-7109-cli-agent-notification.md | 7 ++ docs/agents.md | 4 + docs/settings-reference.md | 4 +- .../cli-agent-permission-notifications.md | 13 +++ .../core/src/__tests__/global-settings.test.ts | 1 + packages/core/src/settings-schema.ts | 1 + packages/core/src/types.ts | 6 ++ .../SettingsModal.remote-notifications.test.tsx | 1 + .../components/__tests__/settings-mobile.test.tsx | 2 +- .../settings/sections/NotificationsSection.tsx | 3 + .../src/__tests__/in-process-runtime.test.ts | 56 ++++++++++- packages/engine/src/__tests__/notifier.test.ts | 6 +- .../engine/src/__tests__/ntfy-provider.test.ts | 4 + .../engine/src/__tests__/webhook-provider.test.ts | 2 + .../engine/src/cli-agent/__tests__/runtime.test.ts | 39 +++++++- .../__tests__/notification-service.test.ts | 56 ++++++++++- .../src/notification/notification-service.ts | 9 +- packages/engine/src/notification/ntfy-provider.ts | 7 ++ .../engine/src/notification/webhook-provider.ts | 2 + packages/engine/src/notifier.ts | 1 + packages/engine/src/runtimes/in-process-runtime.ts | 111 +++++++++++++++++++++ 21 files changed, 326 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-7109 Fusion-Task-Lineage: 636c1875-92a2-46a7-847e-1f4955c0822f Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
04f8fe5d59 |
FN-7107: load CE stage skills for interactive sessions
Ensure Compound Engineering stages request and expose their registered skills when launching interactive sessions. - Add plugin-local skill resolution checks that warn when a stage skill install is missing. - Pass a stable additional skill path and requested stage skill into every CE interactive session. - Add coverage for stage skill loading options and update skill reachability expectations. Files changed: ...d-skills-not-loading-in-interactive-sessions.md | 1 + .../src/__tests__/skill-reachability.test.ts | 71 +++++++++++++--------- .../src/__tests__/stage-skill-loading.test.ts | 69 +++++++++++++++++++++ .../src/session/orchestrator.ts | 48 ++++++++++++++- 4 files changed, 157 insertions(+), 32 deletions(-) Fusion-Task-Id: FN-7107 Fusion-Task-Lineage: 0504e9ba-c2e7-4317-9c90-55693baf8170 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
d9efea9e27 |
docs(workspace): capture Task-field persistence gotcha learning
Adds a docs/solutions learning: a Task field is silently dropped on persist unless it has a SQLite column + defineTaskColumn + rowToTask mapping (applyTaskPatch writes the DB-round-tripped task back over task.json). Plus a CONCEPTS.md entry for the active-session lease (the path-keyed exclusivity/liveness registry whose key choice caused the concurrent-workspace collision). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4faadd4f75 |
Merge remote-tracking branch 'origin/main' into conflict-resolution-1712
# Conflicts: # packages/dashboard/app/components/WorkflowNodeEditor.tsx # packages/engine/src/__tests__/executor-fast-mode-workflows.test.ts |
||
|
|
ec1d29e818 |
FN-6922: guard worktree acquisition returns from repo root
Prevent task worktree acquisition from handing the project root back to executors. - Add a repo-root return guard across resume, pool, and fresh acquisition paths. - Clear invalid repo-root task assignments and create a fresh worktree fallback when safe. - Share repo-root canonicalization and expand regression coverage for acquisition liveness. - Document the acquisition guard and add a patch changeset. Files changed: .changeset/fn-6922-repo-root-acquisition-guard.md | 5 + .../repo-root-task-worktree-requeue-loop.md | 9 +- .../__tests__/executor-worktree-liveness.test.ts | 35 +++ .../worktree-acquisition-secrets-env.test.ts | 9 +- .../src/__tests__/worktree-acquisition.test.ts | 91 ++++++- packages/engine/src/worktree-acquisition.ts | 272 +++++++++++++-------- packages/engine/src/worktree-pool.ts | 8 +- 7 files changed, 304 insertions(+), 125 deletions(-) Fusion-Task-Id: FN-6922 Fusion-Task-Lineage: c16cee1b-de16-4d5f-90e0-132f94fa8377 |
||
|
|
68d3c5820e |
docs(FN-6880): capture optional-group toggle-id collision learning
Document the code-review P1 as a logic-errors learning: a per-task graph toggle (enabledWorkflowSteps, keyed by optional-group node id) collided with the legacy step-template namespace and was silently remapped by the store resolver, bypassing an enabled group. Cross-references the per-task-override blast-radius cousins as the id-namespace-collision variant of that class. Seeds an "Optional step group" entry in CONCEPTS.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7ee1fec22c |
FN-6861: reject repo-root task worktrees
Prevent stale task metadata from treating the project repository root as a reusable task worktree. - Classify root-equal task worktree paths as repo-root before registered-worktree checks. - Clear stale resumed repo-root assignments so acquisition creates a fresh configured task worktree. - Add structured executor audit metadata and regression coverage for repo-root liveness collisions. - Document the repo-root requeue-loop invariant and recovery behavior. Files changed: docs/architecture.md | 2 +- .../repo-root-task-worktree-requeue-loop.md | 49 ++++++++++++++++ .../__tests__/executor-worktree-liveness.test.ts | 23 +++++++- .../src/__tests__/worktree-acquisition.test.ts | 67 +++++++++++++++++++++- .../src/__tests__/worktree-pool-liveness.test.ts | 25 ++++++++ packages/engine/src/executor.ts | 22 ++++++- packages/engine/src/worktree-acquisition.ts | 4 ++ packages/engine/src/worktree-pool.ts | 13 ++++- 8 files changed, 198 insertions(+), 7 deletions(-) Fusion-Task-Id: FN-6861 Fusion-Task-Lineage: d6a4922e-5a21-4037-b2c2-86ff53d8e9e3 |
||
|
|
7e478fb473 |
Merge pull request #1703 from Runfusion/gsxdsm/workflow-optional-steps
feat(dashboard): workflow optional steps — authoring, full-modal parity, stepwise seam |
||
|
|
cb64e874de |
docs: enrich engine singleton-lock learning after PR #1704 review
- Fix frontmatter enums (root_cause: logic_error, component: tooling); add #1699 to related_prs and last_updated - Reflect post-review behavior: external marker cleared at lock-acquire time; reconcile/startAll/onProjectAccessed swallow EngineAlreadyRunningError - Add rejected approach (don't merge has()/hasRunningEngine) and the keep-them-distinct invariant - Tighten socket-name detail (sha1[:16]); cross-link the browser-testing doc Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7d312b416d |
docs: capture engine singleton-lock learning (EngineAlreadyRunningError != no engine)
Document the false "engine not running" banner root cause and fix as a docs/solutions learning, and add an "Engine Singleton Lock" entry to CONCEPTS.md: a failed per-machine lock acquisition is proof an engine is running elsewhere, not "no engine." Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9479f0b313 | docs(solutions): capture eslint exhaustive-deps CI-lint gotcha | ||
|
|
c766bc9c95 |
FN-6811: keep terminal symbols out of xterm metrics
Keep terminal symbol fonts scoped away from xterm's measured text stacks across modal and session surfaces. - add a scoped symbols @font-face to the session terminal CSS chunk - defensively strip the symbols font from resolved xterm font-family presets - expand modal, session, and CSS regression tests for symbols-free measurement and scoped glyph fallback - document recurrence #6 and the remaining real-device iOS Safari verification gap Files changed: .../xterm-symbols-nerd-font-unicode-range.md | 13 ++++-- .../dashboard/app/__tests__/terminal-input.test.ts | 51 ++++++++++++++++++---- .../dashboard/app/components/SessionTerminal.css | 11 +++++ .../dashboard/app/components/TerminalModal.css | 3 ++ .../components/__tests__/SessionTerminal.test.tsx | 3 ++ .../components/__tests__/TerminalModal.test.tsx | 47 ++++++++++++++++++-- .../dashboard/app/utils/terminalPreferences.ts | 17 ++++++-- 7 files changed, 127 insertions(+), 18 deletions(-) Fusion-Task-Id: FN-6811 Fusion-Task-Lineage: 15a6fc1f-34c6-44d4-b9ea-8b5b3baae267 |
||
|
|
2e3b965909 |
FN-6757: smooth mobile Quick Chat keyboard resizing
Smooth Quick Chat mobile panel height changes while keeping viewport sampling synchronous.\n\n- Add Android resize-content detection that toggles height easing only for coarse keyboard viewport samples.\n- Clear smoothing and viewport CSS variables on blur, unmount, and suppressed shrink paths.\n- Cover Android smoothing, iOS offsetTop refocus, Latest gate, and streaming state with Quick Chat regression tests.\n- Document the mobile keyboard smoothing behavior and add a published package changeset.\n\nFiles changed:\n .changeset/smooth-mobile-quick-chat-keyboard.md | 5 +\n docs/dashboard-guide.md | 1 +\n .../quick-chat-mobile-keyboard-board-shift.md | 2 +\n packages/dashboard/app/components/QuickChatFAB.css | 8 ++\n packages/dashboard/app/components/QuickChatFAB.tsx | 17 +++\n .../app/components/__tests__/QuickChatFAB.test.tsx | 141 +++++++++++++++++++++\n 6 files changed, 174 insertions(+) Fusion-Task-Id: FN-6757 Fusion-Task-Lineage: d301aee8-ef76-4d70-a4cd-91641bb05259 |
||
|
|
b9c5b4a2a6 |
docs: capture gate-mode-drops-coverage learning
Document the test-changed shared-infra catch-all trap (gate mode replaces affected-package coverage instead of augmenting it) under docs/solutions/, and add an "Affected-package test selection" concept to CONCEPTS.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
11c4120705 |
FN-6659: keep xterm font metrics symbols-free
Keep terminal wide-glyph fallback out of xterm's measured font path while preserving Nerd Font symbols in DOM rendering. - Split terminal font resolution into symbols-free xterm font families and scoped glyph fallback families. - Apply the glyph fallback CSS variable to both TerminalModal and SessionTerminal surfaces. - Update regression coverage and the documented mobile xterm wide-glyph recurrence analysis. Files changed: .changeset/fn-6659-terminal-render.md | 5 +++ .../xterm-symbols-nerd-font-unicode-range.md | 22 +++++++----- .../dashboard/app/__tests__/terminal-input.test.ts | 42 +++++++++++++--------- .../dashboard/app/components/SessionTerminal.css | 8 +++++ .../dashboard/app/components/SessionTerminal.tsx | 21 +++++++++-- .../dashboard/app/components/TerminalModal.css | 8 +++++ .../dashboard/app/components/TerminalModal.tsx | 13 ++++++- .../__tests__/SessionTerminal.mobile.test.tsx | 6 ++-- .../components/__tests__/SessionTerminal.test.tsx | 14 ++++---- .../components/__tests__/TerminalModal.test.tsx | 9 +++-- .../utils/__tests__/terminalPreferences.test.ts | 2 +- .../dashboard/app/utils/terminalPreferences.ts | 16 +++++---- 12 files changed, 117 insertions(+), 49 deletions(-) Fusion-Task-Id: FN-6659 Fusion-Task-Lineage: e29a709d-9d9d-4a90-9616-8f08c41b126d |