Transfer-status card pattern — bulk-copy dashboards for >60min jobs (parked 2026-05-25)

DARE.CO.UK · PARKED SKETCH · 2026-05-26

Mirrored from ~/.claude/.../memory/parked_sketch_transfer_status_card_pattern_2026-05-25.md. This is a design sketch parked for future build — read for context, not as a current deliverable.

When a bulk copy/migration is expected to run over 60 minutes (RAID ingest, Takeout import, multi-TB rclone, drive-to-drive archive), emit a status card to status.gf.cx/transfers/ with src/dst/throughput/eta/verify fields. Skip the dashboard for short copies. First real instance will be the Immich 4TB→RAID migration; pattern emerged from in-flight 4TB-Evernote archive 2026-05-25.


Dan 2026-05-25: “Short durations dont really need it, but anything over 60 min, feels like a win”

Build trigger

Build the status card emitter + render WHEN ANY:

Below the threshold (Evernote resource-cache at ~100 GB / 24 min, 4K Stogram at 52 GB / 35 min), running progress reporting in the chat is enough — no surface needed.

Card axes (what each transfer surfaces)

Field Why it matters Example
name URL slug + ledger key 4tb-evernote-archive · raid-ingest-dare-takeout
kind Drives badge color + filter; one of archive (delete src) · migration (delete src after period) · sync (keep both) · restore archive
src Where data is moving FROM, with volume context ~/Library/.../Evernote/resource-cache (internal SSD)
dst Where it’s going, with volume + filesystem /Volumes/4TB/archive-from-mac-.../... (APFS, ext)
state Lifecycle machine: queuedstartingcopyingverifyingdone · or failed · paused copying
bytes_done / bytes_total Percent + remaining 45.0 GB / 100 GB (45%)
throughput_inst Last sample MB/s 70 MB/s
throughput_sustained Median over last N samples 65 MB/s
interface What’s actually carrying the bits — load-bearing for Immich planning USB-C (cable-swap 2026-05-25) · TB4 · USB 2.0
eta_seconds Live (bytes_remaining / throughput_sustained) ~21 min
verify Post-copy: file count match · size delta · sha256 sample (optional) 19,311/19,311 files · Δ +22MB · 100 samples ok
started_at / finished_at UTC, ISO-8601 2026-05-25T15:42:11Z ·
interrupted / resume_count Track flakiness across reconnects 0
operator Who kicked it off (manual / launchd / claude-code-session-id) claude:d8367dda-...

Throughput log — the load-bearing side-effect

Append-only ledger per (drive + cable + filesystem) combination — this is what informs Immich pre-flight:

2026-05-25  4TB-USB-C-old-cable      22 MB/s sustained  (4K Stogram 52GB)
2026-05-25  4TB-USB-C-better-cable   65 MB/s sustained  (Evernote 100GB)
2026-XX-XX  TB4-enclosure-RAID       ??? MB/s sustained (first 100GB synthetic)
2026-XX-XX  TB4-enclosure-RAID       ??? MB/s sustained (Takeout dare 200GB)

This is the “empirical throughput data” referenced in parked_sketch_audrey_4tb_photo_library_to_r2_2026-05-24.md — the dashboard is its natural home, not a markdown note.

Two-file architecture (mirrors pa_job_status_update.py shape)

~/bin/transfer_status_update.py
    # CLI: start | progress | verify | done | fail
    # Writes JSON to ~/pa.gf.cx/data/transfers/<slug>.json
    # Appends throughput sample to ~/pa.gf.cx/data/transfers/<slug>.samples.jsonl

~/pa.gf.cx/transfers/render.py
    # Reads all <slug>.json from data/
    # Emits status.gf.cx/transfers/index.html — active grid + archive ledger
    # Per-job page status.gf.cx/transfers/<slug>/ — full timeline + samples chart

CLI shape

transfer_status_update.py start \
    --slug 4tb-evernote-archive \
    --kind archive \
    --src "~/Library/.../Evernote/resource-cache" \
    --dst "/Volumes/4TB/archive-from-mac-2026-05-25/evernote-resource-cache" \
    --bytes-total $(du -sk SRC | awk '{print $1*1024}') \
    --interface "USB-C-better-cable" \
    --operator "claude:d8367dda"

# Every 90s during copy:
transfer_status_update.py progress \
    --slug 4tb-evernote-archive \
    --bytes-done $(du -sk DST | awk '{print $1*1024}')

# After ditto exits:
transfer_status_update.py verify --slug 4tb-evernote-archive \
    --src-files 19311 --dst-files 19311 --delta-kb 22912

transfer_status_update.py done --slug 4tb-evernote-archive

Render shape — sibling to existing status.gf.cx hub

Surface What it shows
status.gf.cx/transfers/ Active transfers grid (1 card each) + archive ledger (last 50, paginated)
status.gf.cx/transfers/<slug>/ Full timeline: started → samples chart → verified → done; 60min+ jobs only
status.gf.cx/ (the meta-hub) Adds a transfers row to the 4-line registry — transfers · last completed 2hr ago · 3 active

Card visual (sketch — uses existing cards primitive)

┌─────────────────────────────────────────────┐
│  ARCHIVE  · copying                          │
│  ┌─────────────────────────────────────┐    │
│  │ 4tb-evernote-archive                 │    │
│  └─────────────────────────────────────┘    │
│                                              │
│  internal SSD ──▶ 4TB external (USB-C)       │
│  Library/.../resource-cache                  │
│  → archive-from-mac-2026-05-25/...           │
│                                              │
│  ▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░  45.0 GB / 100 GB    │
│  65 MB/s sustained · ETA 14 min              │
│                                              │
│  started 15:42:11 · sample 12 of ~50         │
└─────────────────────────────────────────────┘

Match the pa scope-icon library (heraldic SVGs) — use a “freight” or “anchor” icon for transfers.

Why park (not build now)

Pre-build checklist when the time comes

  1. Decide: are throughput samples written to ~/pa.gf.cx/data/transfers/ or to a new domain (e.g. infra.gf.cx/transfers/)? pa. is personal-admin scope; bulk-copy ops might warrant their own infra bucket
  2. ditto vs rsync vs rclone — each has a different progress-emit shape; transfer_status_update.py progress should be invocable by any of them via du -sk DST polling
  3. Failure detection: a copy that’s silent for >5 min while ditto is still alive is suspicious — emit paused state
  4. Verify is non-trivial for large jobs — sha256 sampling, not full-tree (computing 4TB of hashes takes hours)
  5. Throughput-log keying — (filesystem_src, filesystem_dst, interface, cable_label_if_known) — let Dan annotate the cable label at start

Cross-references

The aphorism

Build the dashboard when you’d already be staring at a progress bar for an hour. Below that, terminal updates are dashboard enough. The valuable side-effect — the throughput ledger per drive + cable — accrues either way.

Source: parked_sketch_transfer_status_card_pattern_2026-05-25.md · Rendered 2026-05-26 17:10