The hand-off launch deadlocked its own startup. `resumed` was initialised to
`Boolean(initialSession)`, which is false for a snip, and the idle-resume
effect returned early whenever there was a hand-off, so nothing ever set it.
The render gate needed both `resumed` and `prepared`, so a successful snip
left the view spinning and there was no way to reach the composer.
Two effects guarding two booleans had already produced one bug here, so this
fixes the shape rather than the value:
- One startup effect and one `startup` flag, cleared in a `finally`. Every
branch ends in the same place, including a thrown one, so no path can leave
the view loading.
- The decision tree moves to `lib/startup.ts` as a pure `resolveStartup`,
which takes its storage and file reads as injected ports. It is now covered
by 16 tests, including the case that broke: a hand-off must not fall through
to the idle resume, and must still finish startup.
- `startup.ts` joins the guard that keeps the logic layer free of Vicinae
imports.
- ChatView is prettier-clean; it had been carrying pre-existing drift and has
been rewritten twice this session anyway.
Verified: 273 tests, tsc, eslint, prettier, and vici build.