feat(import): stream real Arcim migration progress as NDJSON (#1485)

* feat(import): stream real Arcim migration progress as NDJSON

The /migrate route ran the orchestrator to completion and answered with one
JSON blob, so the wizard faked its progress bar: a hardcoded 55% anchor and
a static step label for a phase that can take minutes. The orchestrator has
had a real onProgress channel (eight emit points with Swedish step labels
and anchors) since it was written; the route just never passed it.

Now a request with Accept: application/x-ndjson gets a streamed response:
one line per orchestrator progress event, then a terminal done line with
the results or an error line carrying the same structured envelope the
JSON path returns (the 200 status is already committed once the stream
opens). Callers without the header keep the original single-JSON contract,
so pre-deploy tabs and the existing error-mapping tests are untouched.

The wizard opts in, drives MigratingStep from the real labels and anchors
(mapped onto the 55-100 slice of the wizard bar), and treats a dropped
connection as unconfirmed rather than failed, since the migration keeps
running server-side and a blind retry could double-import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: record the opt-in NDJSON streaming decision for /migrate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-10 09:26:27 +02:00
committed by GitHub
co-authored by Claude Fable 5 Jakob Wennberg
parent 4ccebd3645
commit 1d01010928
4 changed files with 322 additions and 19 deletions
+1
View File
@@ -847,4 +847,5 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-08-08] extensions.schema.json enum also gained "stripe" while adding "shopify": the enum had drifted (stripe was enabled in extensions.config.json but missing from the schema, failing editor validation); fixed in the same touch since the file had to change anyway.
[2026-08-08] Login panel is method-stated (BankID hero default, remembered via accounted-login-method cookie) instead of a stacked method list: matches the Swedish bank/Fortnox convention, gives exactly one primary action per view; errors moved from boxed banner to a field-adjacent single line (NN/g 3/4/10), reset link surfaces from the second failed attempt.
[2026-08-09] Upsell FAB dismissal is session-scoped (sessionStorage), not persisted: closing the paywalled sheet or the pill's X hides all floating assistant UI for non-payers until the next browser session; a permanent dismissal would let one click silence the conversion surface forever, and payer/collapsed FAB behavior stays untouched.
[2026-08-09] /migrate streaming is opt-in via Accept: application/x-ndjson instead of replacing the JSON contract: the wizard is the only caller today but a hard cutover would break open pre-deploy tabs and the route's locked error-status tests; mid-stream failures re-send the structured envelope as a terminal error event because the 200 is already committed once the stream opens.
[2026-08-09] Regeluppdat + docs-freshness scans (#1417) built as local loop skills with due-date self-gating, not cloud crons: cloud routines were retired 2026-07-20, and session crons die at 7 days, so weekly/monthly cadence is achieved by loop-ignite running each loop when its run marker says it is due. loop-regeluppdat files tickets only (no auto-fix PRs): regulatory changes touch money math and compliance logic, which .claude/loops.md forbids loops from changing. Docs check diffs the live .md mirror routes against repo-built markdown (exact, canonicalised both sides) instead of diffing the gnubok-website checkout, so it also catches deployed-but-stale and route-404 states.