diff --git a/1030000/META.json b/1030000/META.json index 96c40b1..2ebea54 100644 --- a/1030000/META.json +++ b/1030000/META.json @@ -25,5 +25,5 @@ "stale_leaves": [], "status": "verified", "unverified_leaves": [], - "verified_classmap_sha256": null + "verified_classmap_sha256": "d91fa1f1b40a19c51c5c7781a46e1ac41a44c56f2ea4f10096ce3e289f5f0f85" } diff --git a/1030000/VERIFICATION.md b/1030000/VERIFICATION.md index d5b39aa..770d21b 100644 --- a/1030000/VERIFICATION.md +++ b/1030000/VERIFICATION.md @@ -1,9 +1,42 @@ # Spotify 1.3.0.277 (1030000) -Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS verification + deep CDP e2e on macOS and Windows + targeted live DOM/CSSOM probes. +Pipeline: derive(1020097 -> 1030000) + static target-CSS verification + CDP e2e (deep). Published 2026-09-30. ## Notes +- Classmap derived from 1020097. +- Static verification found 33/36 paths in the target CSS. +- CSS-only misses observed live by their stock class, which remain verified: 3. +- Deep CDP verification observed 23/36 paths with 8/8 successful navigation steps. +- Unresolved new misses remain usable but are marked unverified; stale paths stay blocked. +- Replaces an earlier verification of this key, kept below under Earlier verification. +- Kept the earlier live status of modal.track_credits.container, modal.widget_generator.container, search_chips.wrapper, settings.header.container, settings.section.container, which this run didn't reach. + +## Statistics at publication + +| Field | Value | +| --- | --- | +| leaves | 36 | +| static_present | 33 | +| verified_cdp | 23 | +| live_only | 3 | +| unresolved_missing | 0 | +| cdp_hit_rate | 0.6389 | +| overlay_entries | 291 | + +## Evidence + +- Classmap SHA-256: `d91fa1f1b40a19c51c5c7781a46e1ac41a44c56f2ea4f10096ce3e289f5f0f85` +- Overlay SHA-256: `c0c993277547bfc8ec53f4fa0a055c6e6a470dc5dbb38e37e53ed13514f20e01` +- Target CSS SHA-256: `154bef4f3c5d8c1cd6455a6b1d75e06fa0675f870a35b50e04371ae90a117ce7` +- CDP report generated: 2026-09-29T23:31:53.821Z + +## Earlier verification + +Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS verification + deep CDP e2e on macOS and Windows + targeted live DOM/CSSOM probes. + +### Notes + - Spotify 1.3.0 is a CSS-module rehash, so this key was derived rather than inherited from a 1.2.x classmap. - The 280-entry css-map overlay was generated only from unique exact selector/declaration signatures shared by the stock 1.2.97 and 1.3.0 xpui stylesheets; 82 ambiguous signatures were excluded and 0 generated mappings conflicted with targeted live overrides. - Historical deep CDP runs observed 23/36 paths on stock macOS and 24/36 paths on the patched Windows 1.3.0.277 client. The Windows report records 7/8 successful navigation steps; context-menu activation was not confirmed. @@ -16,7 +49,7 @@ Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS v - The headline statistics and targeted checks describe the historical verification runs. Current candidate coverage is recorded under the macos_2026_09_29 run. - On 2026-09-30 the modal, sort-box and context-menu leaves, which had been stored as their css-map names, were rewritten to their stock hashes from the 291-entry overlay. The css-map stages each back to the same name, and the map is again byte-identical to 1030001. -## Statistics at publication +### Statistics at publication | Field | Value | | --- | --- | @@ -31,7 +64,7 @@ Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS v | cdp_hit_rate | 0.6667 | | overlay_entries | 291 | -## Verification summary at publication +### Verification summary at publication - Deep DOM hits: 24 - Targeted live hits: 12 @@ -42,9 +75,9 @@ Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS v - CSS overlay conflicts: 0 - CSS overlay ambiguous excluded: 82 -## Regression checks +### Regression checks -### topbar_right_button +#### topbar_right_button - Date: 2026-09-15 - Spotify version: 1.3.0.277 @@ -57,7 +90,7 @@ Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS v - Gap (px): 8 - Verification: Measured Bookmark, Full App Display, and Popup Lyrics after Rust apply and a client restart. Their hitboxes and gaps match the native notification and activity buttons. The unchanged 1.2.97 classmap resolves to the same semantic class, whose archived CSS also specifies 32px width and height. -### root_containers +#### root_containers - Date: 2026-09-15 - Spotify version: 1.3.0.277 @@ -71,7 +104,7 @@ Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS v - Method: archived stock CSS inspection - Verification: The embedded CSS-map already maps qPaMr9Jzt0_Doy3C, vEHGULrufZMHBNrp, and YdGOYWQYr6kqh7KU to the same three hooks. Their stock declarations preserve the same grid areas, sizing, and player gutters as 1.3.0; black library and player container backgrounds are already present in 1.2.94. Older version maps remain unchanged. This is archived CSS evidence, not a live older-client check. -### settings_toggle_input +#### settings_toggle_input - Date: 2026-09-15 - Spotify version: 1.3.0.277 @@ -79,7 +112,7 @@ Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS v - Staged class: x-toggle-input - Verification: The native checkbox uses opacity:0, pointer-events:none, and position:absolute in five stock stylesheets. The missing semantic hook left stdlib Settings rows showing both a native checkbox and their styled indicator. Reapply restores the shared input styling; live Spicetify Settings inputs have opacity 0 and absolute positioning while their toggle indicators remain visible. Regression coverage fails without the mapping. -### playback_bar +#### playback_bar - Date: 2026-09-16 - Spotify version: 1.3.0.277 @@ -90,7 +123,7 @@ Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS v - Verification: Stock CSS and live playback controls confirm the container, elapsed-time, and duration roles. Applied through Rust CLI 3.0.0-beta.17 on macOS with published Text 0.1.6. Text restores its full-width 16px seek track. Text 0.1.6 returns the absolutely positioned timestamp spans to normal flow and aligns the seek handle. Pointer seeking, ArrowRight seeking, and the duration/remaining-time toggle passed through UI input. - Previous version check: Archived Spotify 1.2.94 CSS maps o9SONbmdTWwKgbUo, _xaGcuWD6w4FNkGu, and jYB3Yggec0UDIsZh to the same hooks and preserves their container and timestamp roles. Older maps are unchanged. No older client was run live. -### entity_header_backgrounds +#### entity_header_backgrounds - Date: 2026-09-29 - Spotify version: 1.3.0.277 @@ -102,9 +135,9 @@ Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS v - Verification: Stock CSS confirms the roles: kMUtWc is the absolute full-size header layer that carries the inline colour, ELsCw adds the dark gradient on a second kMUtWc layer, M7ECcaA is the 232px z-index -1 gradient behind the action bar, and dqwQhIud is the Home header gradient hidden by its Jyc7 modifier. Spotify 1.3 dropped the separate backgroundColor modifier, so the colour layer maps to the shared background hook. Applied with SPICETIFY_CLASSMAPS_DIR through Rust CLI 3.0.0-beta.17 on macOS: on Burial (avatar header), Liked Songs and Home the readable classes are present, Ziro 0.1.5 hides the action-bar gradient and Home header, and Text renders its flat header. All 14 first-party themes reference these hooks and matched nothing on 1.3.0 before. - Previous version check: The base css-map maps older hashes to the same four hooks; dribbblish and matte select .main-entityHeader-background.main-entityHeader-overlay, matching the two-class overlay layer. No older client was run live. -## Verification runs +### Verification runs -### windows_2026_09_16 +#### windows_2026_09_16 - Classmap SHA-256: `879140c1dddb65b2ff14440c34abe3b2d166274a1309ce3214108e21c8430bc7` - Client state: patched @@ -114,7 +147,7 @@ Pipeline: exact stock-CSS signature migration from 1020097 + static target-CSS v - Navigation succeeded: 7 - Note: Historical evidence for the contributor map before restoring the topbar, navigation-link and outer-search-container roles. -### macos_2026_09_29 +#### macos_2026_09_29 - Spotify version: 1.3.0.277 - OS: macOS diff --git a/1030001/META.json b/1030001/META.json index 44441aa..0542028 100644 --- a/1030001/META.json +++ b/1030001/META.json @@ -27,5 +27,5 @@ "unverified_leaves": [ "settings.header.container" ], - "verified_classmap_sha256": null + "verified_classmap_sha256": "d91fa1f1b40a19c51c5c7781a46e1ac41a44c56f2ea4f10096ce3e289f5f0f85" } diff --git a/1030001/VERIFICATION.md b/1030001/VERIFICATION.md index fb80804..8ce9583 100644 --- a/1030001/VERIFICATION.md +++ b/1030001/VERIFICATION.md @@ -1,9 +1,42 @@ # Spotify 1.3.1.234 (1030001) -Pipeline: inherit(1030000 -> 1030001) + static target-CSS verification + CDP e2e (deep). +Pipeline: inherit(1030000 -> 1030001) + static target-CSS verification + CDP e2e (deep). Published 2026-09-30. ## Notes +- Classmap inherited byte-for-byte from 1030000; no migration guesses were accepted. +- Static verification found 32/36 paths in the target CSS. +- CSS-only misses observed live by their stock class, which remain verified: 3. +- Deep CDP verification observed 23/36 paths with 7/8 successful navigation steps. +- Unresolved new misses remain usable but are marked unverified; stale paths stay blocked. +- Replaces an earlier verification of this key, kept below under Earlier verification. +- Kept the earlier live status of settings.section.container, which this run didn't reach. + +## Statistics at publication + +| Field | Value | +| --- | --- | +| leaves | 36 | +| static_present | 32 | +| verified_cdp | 23 | +| live_only | 3 | +| unresolved_missing | 1 | +| cdp_hit_rate | 0.6389 | +| overlay_entries | 291 | + +## Evidence + +- Classmap SHA-256: `d91fa1f1b40a19c51c5c7781a46e1ac41a44c56f2ea4f10096ce3e289f5f0f85` +- Overlay SHA-256: `c0c993277547bfc8ec53f4fa0a055c6e6a470dc5dbb38e37e53ed13514f20e01` +- Target CSS SHA-256: `674b4e92bd00a513cef999978933dc49c8d31cff42c6e5b275b0960741b1a978` +- CDP report generated: 2026-09-29T23:31:18.380Z + +## Earlier verification + +Pipeline: inherit(1030000 -> 1030001) + static target-CSS verification + CDP e2e (deep). + +### Notes + - Classmap inherited byte-for-byte from 1030000; no migration guesses were accepted. - Static verification found 32/36 paths in the target CSS. - 3 CSS-only misses were observed live and remain verified. @@ -12,7 +45,7 @@ Pipeline: inherit(1030000 -> 1030001) + static target-CSS verification + CDP e2e - The CSS overlay includes the four entity-header, action-bar and Home background hooks added to 1030000 on 2026-09-29. Their selectors and layout declarations also occur in the signed Spotify 1.3.1.234 macOS ARM64 stock archive; the 36-leaf classmap remains unchanged. - The headline statistics describe the original promotion. The patched macOS candidate run and its narrower observed coverage are recorded under Verification runs. -## Statistics at publication +### Statistics at publication | Field | Value | | --- | --- | @@ -26,14 +59,14 @@ Pipeline: inherit(1030000 -> 1030001) + static target-CSS verification + CDP e2e | cdp_hit_rate | 0.6667 | | overlay_entries | 291 | -## Verification summary at publication +### Verification summary at publication - Needs manual check: 32 - Missing in CSS: 4 -## Verification runs +### Verification runs -### macos_arm64_2026_09_29 +#### macos_arm64_2026_09_29 - Spotify version: 1.3.1.234 - Client state: patched diff --git a/index.json b/index.json index b4dbed7..a621446 100644 --- a/index.json +++ b/index.json @@ -157,7 +157,7 @@ }, "meta": { "file": "META.json", - "sha256": "a22d944c39a622ae22d884eda92cc9c3949b6d2e338374ae9dc7a09d12d48fe9" + "sha256": "72ee06ca7b7ba2549941e6e5f3e72576b2484f279b294f35a5c7183a90f38e2a" }, "spotifyVersion": "1.3.0.277", "status": "verified" @@ -173,7 +173,7 @@ }, "meta": { "file": "META.json", - "sha256": "b83a268923007a87f80eb9384f515d6f4622284751a25270408f9337d353fb62" + "sha256": "8fb8a1390481a02cd77fec4b4a00562f8d012fc65f96b7b10ead8153b65852eb" }, "spotifyVersion": "1.3.1.234", "status": "verified" diff --git a/scripts/publish-key.test.mts b/scripts/publish-key.test.mts index ee6a4cf..fcfa4fc 100644 --- a/scripts/publish-key.test.mts +++ b/scripts/publish-key.test.mts @@ -316,9 +316,25 @@ test("--replace re-verifies a key, keeping its first publication date", () => { assert.equal(meta.generated, "2026-08-12"); assert.equal(meta.required_paths["main.playbar.indicator"], "verified_cdp"); assert.ok(readVerification(target).includes("Replaces an earlier verification")); + const [current, earlier] = readVerification(target).split("## Earlier verification\n"); + assert.ok(current.includes("| verified_cdp | 2 |")); + assert.ok(earlier.includes("### Notes") && earlier.includes("| verified_cdp | 1 |"), "the replaced history is kept, one level deeper"); assert.deepEqual(stagingDirs(), []); }); +test("--replace keeps an earlier live status the new run didn't reach", () => { + publishRelease(options()); + setHit("main.topbar.wrapper", false, { hits: 0, hitRate: 0 }); + setHit("main.playbar.indicator", false, {}); + assert.throws(() => publishRelease(options({ replace: true })), /below required/); + const reports = reportsFor(classmap, "1.2.96.518", new Set(["main.topbar.wrapper", "main.playbar.indicator"]), new Set(["main.playbar.indicator"])); + const target = publishRelease(options({ replace: true, staticReport: reports.static, cdpReport: reports.cdp })); + const meta = readMeta(target); + assert.equal(meta.required_paths["main.topbar.wrapper"], "verified_cdp", "unchanged class, earlier live hit"); + assert.equal(meta.required_paths["main.playbar.indicator"], "verified_cdp"); + assert.ok(readVerification(target).includes("Kept the earlier live status of main.topbar.wrapper")); +}); + test("--replace refuses a map that would break the keys inheriting from it", () => { publishRelease(options()); const changed = { ...classmap, main: { ...classmap.main, playbar: { indicator: "otherHashDD" } } }; diff --git a/scripts/publish-key.ts b/scripts/publish-key.ts index 26a57f9..8ad6c07 100644 --- a/scripts/publish-key.ts +++ b/scripts/publish-key.ts @@ -298,6 +298,17 @@ function verificationMarkdown(o: { ].join("\n"); } +/** Appends a replaced key's previous VERIFICATION.md, its headings one level deeper. */ +function withHistory(verification: string, targetDir: string): string { + const file = path.join(targetDir, "VERIFICATION.md"); + if (!isFile(file)) return verification; + const previous = readUtf8(file) + .replace(/^# .*\n+/, "") + .replace(/^(#+) /gm, "#$1 ") + .trimEnd(); + return `${verification}\n## Earlier verification\n\n${previous}\n`; +} + function newestVerifiedKey(root: string, below: string): string | undefined { return keyDirs(root) .filter((key) => key < below && isFile(path.join(root, key, "META.json")) && readMeta(root, key).status === "verified") @@ -331,6 +342,12 @@ export function publishRelease(o: PublishOptions, index = false): string { if (exists && !o.replace) throw new Error(`${targetKey} already exists; pass --replace to re-verify it`); if (!exists && o.replace) throw new Error(`${targetKey} does not exist, so there is nothing to replace`); const generated = exists ? (readMeta(o.root, targetKey).generated ?? o.generated) : o.generated; + const replaced = exists + ? { + meta: readMeta(o.root, targetKey), + values: leafValues(JSON.parse(readUtf8(path.join(targetDir, "classmap.json")))), + } + : null; const sourceKey = o.inheritFrom ?? o.derivedFrom ?? null; const sourceMeta: Meta = sourceKey ? readMeta(o.root, sourceKey) : {}; @@ -398,7 +415,7 @@ export function publishRelease(o: PublishOptions, index = false): string { } const observedStale = [...stale].filter((p) => live.has(p)).sort(); if (observedStale.length) notes.push(`Marked stale although observed live: ${observedStale.join(", ")}.`); - if (exists) notes.push(`Replaces an earlier verification of this key; its history is in git.`); + if (exists) notes.push("Replaces an earlier verification of this key, kept below under Earlier verification."); if (liveOverlaySha !== undefined && liveOverlaySha !== overlaySha) { throw new Error("the CDP report was run with a different overlay than the one being published"); } @@ -407,6 +424,23 @@ export function publishRelease(o: PublishOptions, index = false): string { } const unverified = new Set([...doubted].filter((p) => !live.has(p) && !stale.has(p))); + // Earlier live evidence for an unchanged class on the same build still holds + // when the new run didn't reach that surface. + const statuses = requiredStatuses(required, unverified, live); + if (replaced) { + const kept: string[] = []; + for (const [leaf, status] of Object.entries(statuses)) { + const before = replaced.meta.required_paths?.[leaf]; + const unchanged = replaced.values.get(leaf) === values.get(leaf); + if (status !== "verified_cdp" && (before === "verified_cdp" || before === "verified_targeted") && unchanged && !stale.has(leaf)) { + statuses[leaf] = before; + unverified.delete(leaf); + kept.push(leaf); + } + } + if (kept.length) notes.push(`Kept the earlier live status of ${kept.sort().join(", ")}, which this run didn't reach.`); + } + const method = inherited ? "inherited" : "derived"; const meta = { schema_version: 2, @@ -415,7 +449,7 @@ export function publishRelease(o: PublishOptions, index = false): string { status: "verified", generated, source: { method, key: sourceKey }, - required_paths: requiredStatuses(required, unverified, live), + required_paths: statuses, stale_leaves: [...stale].sort(), unverified_leaves: [...unverified].sort(), verified_classmap_sha256: sha256(classmapBytes), @@ -444,7 +478,7 @@ export function publishRelease(o: PublishOptions, index = false): string { writeFileSync(path.join(staging, "classmap.json"), classmapBytes); if (overlayText) writeText(path.join(staging, "css-map.json"), overlayText); writeText(path.join(staging, "META.json"), renderJson(meta)); - writeText(path.join(staging, "VERIFICATION.md"), verification); + writeText(path.join(staging, "VERIFICATION.md"), exists ? withHistory(verification, targetDir) : verification); const errors = keyErrors(staging, targetKey, o.root); if (errors.length) throw new Error(`published key fails validation: ${errors.join("; ")}`); if (exists) {