* fix(snapshot): prevent drift corruption from duplicate installs + boot-time mismatch (#161, #162)
Closes#162 and #161 as the paired fix the issues describe — both touch
the same data-loss surface and verify against the same dev-sync rig.
installs at import time, log critical, register nothing in the
alphabetical loser; client-side `window` sentinel as belt-and-suspenders;
align .env.example / dev-iteration-loop / sync_to_dev / README at the
Registry-derived `koolook/` path.
compare against live /userdata + picks, set a drift flag on mismatch;
gate periodic + pre-load autosave routing to `_unsaved_autosave/` while
drifted; promote status pill to "drifted" precedence over "saved" with a
recovery tooltip; auto-clear on the next markStateSaved (Save / Load).
26 new tests (14 install guard, 4 sidebar sentinel, 8 boot drift) —
316 total passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(snapshot): apply review fixes — canonical fingerprint + early loser exit + drift timestamp
Review-driven follow-ups on PR #186:
1. **Symmetrize file-side fingerprint (MEDIUM).** `detectBootDrift`
now uses a new `_canonicalStateFingerprint()` that normalizes the
workflows tree through `normalizeWorkflowsStore` AND deep-sorts keys
via `sortJsonValue`. Without this, hand-edited / cross-machine /
pre-v0.3 files would false-positive as drifted on first boot
(e.g. `archived: "false"` string vs `false` bool, missing
`directories: {}`, key insertion-order asymmetry from the
`{...wf, archived, module, tags}` spread). `_computeFingerprint`
stays unchanged so the localStorage save baseline contract holds
for existing users. New regression test verifies a shape-only diff
does NOT trigger drift.
2. **Gate fork imports on `_is_winning_install` (LOW).** Non-winning
duplicate installs now short-circuit before the heavy torch / PIL /
diffusers / aiohttp imports, exiting in ~5 LoC instead of paying
full import cost just to discard the mappings.
3. **Drift timestamp uses `driftDetectedAt`, not stale
`lastNamedSaveAt` (LOW).** The localStorage save baseline could be
from a prior session whose live state was already corrupt —
surfacing it as "Last named save" in the drift tooltip would
mislead. Replaced with the session-noticed-at timestamp from
`_bootDriftDiagnostics`.
352 tests passing (316 prior + 26 from PR #186 + 1 new normalize-
symmetry case).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(install-guard): fail safe on unreadable / non-UTF-8 siblings (#162)
Re-review follow-up. The import-time duplicate-install scan could crash
the entire plugin load (fail-silent — the alphabetical winner vanished
too, not just the loser) on two unguarded paths:
- detect_duplicate_koolook_installs: the per-entry is_dir()/is_file()
over custom_nodes/ siblings was unguarded, so a non-traversable
sibling (restrictive perms, broken mount, a special dir) raised
PermissionError up into __init__.py. No duplicate even required.
Now caught per-entry and skipped.
- read_pyproject_version: read_text(encoding="utf-8") raises
UnicodeDecodeError (a ValueError, NOT OSError) on a non-UTF-8 /
binary pyproject.toml, escaping the `except OSError`. Now degrades
to "?" via `except (OSError, ValueError)`.
- __init__.py: wrapped the guard call site as a final backstop — any
unforeseen guard failure now degrades to register-normally rather
than aborting the import.
Both paths reproduced before the fix; the first surfaced while running
the suite from a /tmp checkout whose parent held a root-owned dir.
3 regression tests added (non-traversable sibling skipped; non-UTF-8
pyproject -> "?"; report survives a non-UTF-8 sibling). Full suite:
452 passed (449 + 3).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: kflame99 <261674183+kflame99@users.noreply.github.com>