Files
fusion/packages/engine
gsxdsm 1824c04584 fix(core): restore an archived card to the lane it came from (#2832)
## What

Fixes the defect #2824 measured. That PR's characterization cases —
merged and asserting the wrong-but-real behaviour — are flipped here to
the correct lanes, which is what they were written to do.

## The bug

```ts
// archive-lifecycle-2.ts
const preArchiveColumn = task.preArchiveColumn ?? "todo";
```

**`preArchiveColumn` has no database column.** It exists on the `Task`
type and in the archive snapshot, and nowhere else — so the in-place
restore cannot carry it, `store.getTask(id)` reads a live row that never
had it, and the literal decided the destination for **every unarchive
that has ever run**.

| board | what happened |
|---|---|
| **default** | `todo` is declared, so the resolver returned it.
Restores landed in the queue and **looked right**. |
| **custom** | `todo` is declared nowhere, so the resolver took its "no
usable history" branch and returned the **complete** lane. A card
archived mid-implementation came back marked **finished**. |

That coincidence is why this survived **three** separate fixes to
`resolveUnarchiveTargetColumnImpl` — a `?? "done"` that invented a
column, an `isColumn` legacy-enum gate, and the same gate one function
over. Every one was correcting how the resolver interprets a value that
never arrived.

## The fix is two halves, and either alone does nothing

1. **Capture** — `taskToArchiveEntryImpl` records `task.column` into the
snapshot. That is the last place the original is still in hand, since
the entry's own `column` is set to `"archived"` on the line above.
2. **Read** — `unarchiveTaskImpl` reads the **snapshot it already
loaded**, not the restored row.

I shipped half of this first and watched the destination stay wrong,
which is how I found that the field has no row to live on. Mutation
matrix:

| state | result |
|---|---|
| both halves | **5/5 pass** |
| capture only (read reverted) | **3 fail** |
| read only (capture reverted) | **3 fail** |

I also tried carrying it through `restoreTaskFromArchive`'s row update —
that fails to typecheck, which is the proof that no such column exists
and the snapshot is the only source.

## Behaviour changes, deliberately

- **Custom boards** — a card returns to the lane it was archived from
instead of appearing finished.
- **Default board** — a card archived from `done` restored to `todo`
under the literal and now restores to `done`. Returning finished work to
the queue was the fallback showing through, not a rule anyone chose; the
resolver's own branches say a card archived from a declared column goes
back to it.

## One expectation of mine was wrong, and the resolver was right

I expected a card archived from the review lane to return to **hold**.
It returns to the review lane, and that is correct: `.review` is derived
from the `mergeOrchestration` flag, **not** from `human-review`. The
fixture's review column declares `human-review` + `merge-blocker` only,
so it is not a `.review` lane to the resolver — just a declared column
with usable history. The case now asserts that with the reasoning
attached, so the next reader does not "fix" it back to hold.

## Verification

- unarchive suite — **5/5**, mutation matrix above
- `pnpm test:gate` — **exit 0**
- full live-PG E2E surface — **164/164**
- `pnpm lint` — clean

Changeset included (`patch`, category `fix`).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 13:54:53 -07:00
..
2026-07-26 18:11:47 -07:00