Repository navigation
feat(porter): manifest content hashes + import preview diff, driven validate - #80
Merged
Merged
Conversation
Backups now carry SHA-1 digests of their content in the manifest
(hashes: {settings, data}; sha1 computed during export streaming,
manifest written last), and the import preview shows a per-target
diff between the machine's current content and the backup: settings
and data (db + drivers folded) each get an identical/changed/new
state with sizes and counts.
- pkg/porter: hash fold recipe (UTF-8 byte-length framing, frame-string
sort, last-wins arcname dedupe, empty keys omitted), ValidateZip
split into a ZIP-only core (imports never re-hash the disk) plus a
synchronous live-FS compare that degrades to "unavailable" on read
errors; legacy backups without hashes hide the diff rows.
- it-claws (separate repo, mirrored there): the same recipe writes
hashes when building exports; cross-repo parity is pinned by a
golden test on a CJK-arcname fixture.
- frontend: diff rows on the preview checklist, `compare` step in the
download stepper, re-entrancy guard on validate, en + zh_Hant_HK
i18n; bindings regenerated.
Verified: go test ./pkg/... green, frontend type-check/lint/vitest
(121) green, wails generate clean.
- ValidateZip follows the same job lifecycle as Export/DownloadAndValidate so the frontend can poll it; previously local hashing ran job-less and the UI appeared frozen on large driver sets. - localDataHashes splits into a stat-only listing pass and a hashing pass, reporting byte progress per file (tick) and honoring Abort mid-walk; buildPreview/dataDiff propagate only the cancel sentinel, local read failures still hide the row (both settings and data). - DownloadAndValidate removes the temp ZIP on any post-download failure (temp file leaked on the new compare error paths before this). - frontend shares one runPreviewJob driver for download/validate exposes (was a verbatim 25-line clone), validate wired through ProgressModal, dead 'unavailable' DiffState removed, fold-key invariant documented in action.go, stale step-list comment in JobSnapshot dropped. - drop committed session handoff doc.
markmybytes
force-pushed
the
feat/preview-diff-manifest-hashes
branch
from
November 5, 2026 13:38
868df24 to
4540f06
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Import preview now tells you whether the ZIP's settings/data actually differ from what's on disk before you import — and export stamps the content hashes that make it possible.
Backend (
pkg/porter)hash.go):toZipcomputes a per-entry sha1 while packing (singleio.MultiWriterpass) and writesmanifest.jsonlast, once the digest of entries is known:hashes.settings— sha1 of theconf/setting.jsonentry byteshashes.data— folded digest overconf/data.db+drivers/**: frames<byte-len-of-arcname>:<arcname>:<sha1hex>\nsorted byte-wise; empty fold stays absent (omitempty)hash_golden_test.go(mirrors it-clawstests/test_hashes.py; CJK/Cyrillic arcname exercises the byte-length prefix). If it fails, the recipe drifted and must be reported — not silently fixed.porter.go):zipScan(cheap, central directory + manifest, no filesystem) is shared by the validate and import paths;buildPreviewaugments it with twoTargetDiffrows (settings/data): incoming/local present, counts, sizes,identical.available=false, the row is simply hidden. Only cancellation errors out (sentinelerrImportCanceled, shared with the extract paths;errors.Is-checked indataDiff).progress.go, newtickinhash.go): local hashing reads every byte of the local driver set, which previously ran job-less and froze the UI on large packs.ValidateZipnow follows the same job lifecycle asExport/DownloadAndValidate;localDataHashesis two-phase (stat-only listing → hash pass) reporting byte progress per file and honoringAbortmid-walk.DownloadAndValidatenow removes the temp ZIP on any post-download failure (added compare paths could previously leak it).Frontend
identical/changed/newbadge + incoming/local facts per category (porter.vue); rows hidden whenavailableis false.ProgressModal(newvalidateexpose sharing onerunPreviewJobdriver withdownload): stepper shows Init → Compare → Complete with a live byte-progress bar and a working Cancel.comparestep key added toProgressStepper; i18n keys added inen.json+zh_Hant_HK.json(only existing keys reused otherwise). Deadunavailablediff state removed.Known limits (intentional)
Drivers\directory vice zipdrivers/shows "changed" — preview-only cosmetics.Tests
go test ./pkg/...— full suite green on Windows; 5 new porter tests for fold/progress/abort/semantics (diff_internal_test.go, expandedporter_test.go,hash_golden_test.go)vue-tsc --noEmitclean,eslint .clean (3 pre-existing unrelated warnings), 121/121 vitest pass