← back to Quadrille Showroom
Slice-3 H: globally-unique element build codes (1000QH<version>)
1b5eb937a623b655fdfc9aa18b0bad202d6fffa1 · 2026-06-28 18:46:17 -0700 · Steve Abrams
assignElementCodes() runs once at registry load — iterates VERSION_ORDER, each
version's elements by ascending n, handing out base-1000 global seq across all 11
builds; stamps elt.code (e.g. seq 0 + QH + V2 -> 1000QHV2). Deterministic + stable.
BUILD_CODE='QH' isolated to one swappable constant. Pin identity, Chosen tray, and
Copy-as-text now key on the durable global code, not the version-local 'V1.4' key
(which collided across versions). Pins show the code chip + friendly badge n.
qh_chosen localStorage migrates old key-only rows (backfills code, never crashes).
Files touched
M public/css/showroom.cssM public/js/versions.js
Diff
commit 1b5eb937a623b655fdfc9aa18b0bad202d6fffa1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jun 28 18:46:17 2026 -0700
Slice-3 H: globally-unique element build codes (1000QH<version>)
assignElementCodes() runs once at registry load — iterates VERSION_ORDER, each
version's elements by ascending n, handing out base-1000 global seq across all 11
builds; stamps elt.code (e.g. seq 0 + QH + V2 -> 1000QHV2). Deterministic + stable.
BUILD_CODE='QH' isolated to one swappable constant. Pin identity, Chosen tray, and
Copy-as-text now key on the durable global code, not the version-local 'V1.4' key
(which collided across versions). Pins show the code chip + friendly badge n.
qh_chosen localStorage migrates old key-only rows (backfills code, never crashes).
---
public/css/showroom.css | 21 +++++--
public/js/versions.js | 144 +++++++++++++++++++++++++++++++++++++++---------
2 files changed, 134 insertions(+), 31 deletions(-)
diff --git a/public/css/showroom.css b/public/css/showroom.css
index 8a0bd6a..6ef5695 100644
--- a/public/css/showroom.css
+++ b/public/css/showroom.css
@@ -486,12 +486,24 @@ body { overflow: hidden; background: #0a0a0f; font-family: 'Segoe UI', system-ui
transition: transform .12s, background .12s, box-shadow .12s;
}
.el-pin:hover .pin-badge { transform: scale(1.12); box-shadow: 0 4px 16px rgba(0,0,0,0.6), 0 0 0 4px rgba(201,169,110,0.30); }
+/* GLOBALLY-UNIQUE element code (1003QHV1) — ALWAYS visible on a numbered pin so
+ every element shows its durable cross-build ID, alongside the friendly badge n. */
+.el-pin .pin-code {
+ display: inline-flex; align-items: center; white-space: nowrap;
+ margin-left: 5px; padding: 3px 7px; border-radius: 7px;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
+ font-size: 10.5px; font-weight: 700; letter-spacing: .4px; line-height: 1;
+ color: #f0e6cf; background: rgba(14,12,18,0.86);
+ border: 1px solid rgba(201,169,110,0.40);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.45);
+}
+.el-pin.chosen .pin-code { color: #1a1206; background: linear-gradient(180deg,#d8bd84,#b9933f); border-color: #fff0d2; }
.el-pin .pin-label {
max-width: 0; overflow: hidden; white-space: nowrap;
font-size: 12px; font-weight: 600; letter-spacing: .3px; color: #f5efdc;
background: rgba(14,12,18,0.86); border: 1px solid rgba(201,169,110,0.30);
- border-left: none; border-radius: 0 9px 9px 0; line-height: 1;
- margin-left: -4px; padding: 0; opacity: 0;
+ border-radius: 9px; line-height: 1;
+ margin-left: 5px; padding: 0; opacity: 0;
transition: max-width .18s ease, opacity .15s ease, padding .18s ease;
}
.el-pin:hover .pin-label, .el-pin.show-label .pin-label {
@@ -546,8 +558,9 @@ body { overflow: hidden; background: #0a0a0f; font-family: 'Segoe UI', system-ui
background: rgba(255,255,255,0.05); border: 1px solid rgba(201,169,110,0.26);
}
.ct-chip .ctc-tag {
- font-size: 11px; font-weight: 800; letter-spacing: .5px; color: #c9a96e;
- font-variant-numeric: tabular-nums; min-width: 42px;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
+ font-size: 10.5px; font-weight: 800; letter-spacing: .4px; color: #c9a96e;
+ font-variant-numeric: tabular-nums; flex: 0 0 auto; white-space: nowrap;
}
.ct-chip .ctc-name {
flex: 1; font-size: 12.5px; color: #ede4cf; line-height: 1.2;
diff --git a/public/js/versions.js b/public/js/versions.js
index fcccf02..a44018a 100644
--- a/public/js/versions.js
+++ b/public/js/versions.js
@@ -340,16 +340,80 @@ function boot() {
};
const VERSION_ORDER = ['V1','V2','V3','V4','V5','V6','V7','V8','V9','V10','V11'];
+ // ===========================================================================
+ // GLOBALLY-UNIQUE ELEMENT CODES (Slice-3 / chunk H)
+ // ---------------------------------------------------------------------------
+ // Steve's rule: every numbered element gets a GLOBALLY-UNIQUE build code that
+ // is unique across ALL 11 builds, base 1000, carrying the build code + the
+ // element's version. Canonical example: element 1 → `1000QHV2` =
+ // `${1000 + globalSeq}` + `${BUILD_CODE}` + `${version}`.
+ //
+ // BUILD_CODE is ONE swappable constant. Steve typed "build code CH" but his
+ // concrete worked example is `1000QH…`, so we use 'QH' per the literal example
+ // and isolate it here — flip to 'CH' is a single-char change if Steve confirms.
+ const BUILD_CODE = 'QH';
+ //
+ // assignElementCodes() runs ONCE at registry load: it iterates VERSION_ORDER
+ // (deterministic, stable order) → each version's `elements` by ASCENDING `n`,
+ // handing out global sequence 1000, 1001, 1002 … and stamping `elt.code` on
+ // every element. Because VERSION_ORDER and each elements[] array are fixed
+ // literals, the same element ALWAYS gets the same code on every page load,
+ // independent of click order or which version is opened.
+ // code = `${1000 + globalSeq}${BUILD_CODE}${version}` e.g. seq 0, V2 → 1000QHV2
+ function assignElementCodes() {
+ let globalSeq = 0;
+ VERSION_ORDER.forEach(vk => {
+ const v = VERSIONS[vk];
+ if (!v || !Array.isArray(v.elements)) return;
+ // Ascending-n is the friendly index order; elements are authored in n-order
+ // already, but sort defensively so a future out-of-order edit can't shuffle codes.
+ v.elements.slice().sort((a, b) => a.n - b.n).forEach(elt => {
+ elt.code = '' + (1000 + globalSeq) + BUILD_CODE + vk;
+ globalSeq++;
+ });
+ });
+ }
+ assignElementCodes();
+
+ // Look up an element's code from its version + n (for migrating old chosen rows
+ // and for cross-checks). Returns null if the element no longer exists.
+ function codeFor(version, n) {
+ const v = VERSIONS[version];
+ if (!v || !Array.isArray(v.elements)) return null;
+ const elt = v.elements.find(e => e.n === n);
+ return elt ? elt.code : null;
+ }
+
// ===========================================================================
// STATE
// ===========================================================================
let currentVersion = null;
let lastThreeDVersion = 'V1'; // last in-engine version (so Back from a mechanic restores a real scene)
let overlayOn = false;
- let chosen = loadChosen(); // [{ key:'V1.4', version:'V1', n:4, label:'…' }]
+ let chosen = loadChosen(); // [{ code:'1003QHV1', version:'V1', n:4, label:'…' }]
+ // IDENTITY (Slice-3 / chunk H): a chosen element is keyed by its globally-unique
+ // `code` (e.g. 1003QHV1), NOT the old version-local 'V1.4' key (which collided
+ // across versions). loadChosen() tolerates OLD localStorage rows that only carried
+ // `key` — it backfills `code` from {version,n} where the element still exists, and
+ // keeps any it can't resolve so an old pick stays displayable instead of crashing.
function loadChosen() {
- try { return JSON.parse(localStorage.getItem('qh_chosen') || '[]'); } catch (e) { return []; }
+ let raw;
+ try { raw = JSON.parse(localStorage.getItem('qh_chosen') || '[]'); } catch (e) { return []; }
+ if (!Array.isArray(raw)) return [];
+ return raw.map(c => {
+ if (!c || typeof c !== 'object') return null;
+ if (c.code) return c; // already migrated
+ // Old shape: derive version/n from {version,n} or a 'V1.4' key, then backfill code.
+ let version = c.version, n = c.n;
+ if ((version == null || n == null) && typeof c.key === 'string' && c.key.indexOf('.') > 0) {
+ const parts = c.key.split('.');
+ version = version != null ? version : parts[0];
+ n = n != null ? n : parseInt(parts[1], 10);
+ }
+ const code = (version != null && n != null) ? (codeFor(version, n) || c.key || ('?' + (c.key||''))) : (c.key || '?');
+ return { code, version: version != null ? version : null, n: n != null ? n : null, label: c.label || '' };
+ }).filter(Boolean);
}
function saveChosen() {
try { localStorage.setItem('qh_chosen', JSON.stringify(chosen)); } catch (e) {}
@@ -443,16 +507,19 @@ function boot() {
const back3d = lastThreeDVersion || 'V1';
setVersion(back3d);
} else if (d.type === 'choose') {
- const key = d.version + '.' + d.n;
- const exists = isChosen(key);
- if (d.chosen && !exists) { chosen.push({ key, version: d.version, n: d.n, label: d.label }); bumpCount(); }
- else if (!d.chosen && exists) { const i = chosen.findIndex(c => c.key === key); if (i >= 0) chosen.splice(i, 1); }
+ // Protos still send {version,n}; resolve to the global code (overlay mechanics'
+ // legends key locally by n, but the Chosen tray + copy use the durable code).
+ const code = codeFor(d.version, d.n) || (d.version + '.' + d.n);
+ const exists = isChosen(code);
+ if (d.chosen && !exists) { chosen.push({ code, version: d.version, n: d.n, label: d.label }); bumpCount(); }
+ else if (!d.chosen && exists) { const i = chosen.findIndex(c => c.code === code); if (i >= 0) chosen.splice(i, 1); }
saveChosen(); renderTray();
} else if (d.type === 'ready') {
- // sync the current chosen set for THIS version into the proto's legend
+ // sync the current chosen set for THIS version into the proto's legend. The proto
+ // legend keys by the local 'V_.n' id, so map our chosen rows back to that shape.
const frame = document.getElementById('mech-frame');
if (frame && frame.contentWindow) {
- const keys = chosen.filter(c => c.version === d.version).map(c => c.key);
+ const keys = chosen.filter(c => c.version === d.version).map(c => c.version + '.' + c.n);
try { frame.contentWindow.postMessage({ __qhPicker: true, type: 'sync-chosen', keys }, '*'); } catch (e) {}
}
}
@@ -462,7 +529,7 @@ function boot() {
// NUMBERED-ELEMENT OVERLAY — circled-number pins projected onto each element.
// ===========================================================================
let pinLayer = null;
- let activePins = []; // [{ el, anchor, n, label, key }]
+ let activePins = []; // [{ el, anchor, n, label, code }]
function anchorWorld(anchor) {
if (!anchor) return null;
@@ -484,17 +551,21 @@ function boot() {
if (!pinLayer) { pinLayer = document.getElementById('pin-layer'); }
if (!pinLayer) return;
v.elements.forEach(elt => {
- const key = currentVersion + '.' + elt.n;
+ // IDENTITY = the globally-unique code (1003QHV1…), NOT the old version-local
+ // 'V1.4' key. The friendly circled number `n` stays on the badge; the code
+ // rides in a sub-label so every visible pin shows its durable cross-build ID.
+ const code = elt.code || (currentVersion + '.' + elt.n); // codeFor-equivalent (already assigned)
const pin = document.createElement('div');
pin.className = 'el-pin';
- pin.dataset.key = key;
+ pin.dataset.code = code;
pin.innerHTML =
'<span class="pin-badge">' + elt.n + '</span>' +
+ '<span class="pin-code" title="Globally-unique element code">' + escapeHtml(code) + '</span>' +
'<span class="pin-label">' + escapeHtml(elt.label) + '</span>';
- if (isChosen(key)) pin.classList.add('chosen');
- pin.addEventListener('click', (e) => { e.stopPropagation(); toggleChoice(key, currentVersion, elt.n, elt.label, pin); });
+ if (isChosen(code)) pin.classList.add('chosen');
+ pin.addEventListener('click', (e) => { e.stopPropagation(); toggleChoice(code, currentVersion, elt.n, elt.label, pin); });
pinLayer.appendChild(pin);
- activePins.push({ el: pin, anchor: elt.anchor, n: elt.n, label: elt.label, key });
+ activePins.push({ el: pin, anchor: elt.anchor, n: elt.n, label: elt.label, code });
});
positionPins(); // place immediately
}
@@ -547,18 +618,19 @@ function boot() {
// ===========================================================================
// CHOSEN ELEMENTS TRAY
// ===========================================================================
- function isChosen(key) { return chosen.some(c => c.key === key); }
- function toggleChoice(key, version, n, label, pinEl) {
- const i = chosen.findIndex(c => c.key === key);
+ // Chosen-element identity is the globally-unique `code` (1003QHV1…).
+ function isChosen(code) { return chosen.some(c => c.code === code); }
+ function toggleChoice(code, version, n, label, pinEl) {
+ const i = chosen.findIndex(c => c.code === code);
if (i >= 0) { chosen.splice(i, 1); if (pinEl) pinEl.classList.remove('chosen'); }
- else { chosen.push({ key, version, n, label }); if (pinEl) pinEl.classList.add('chosen'); bumpCount(); }
+ else { chosen.push({ code, version, n, label }); if (pinEl) pinEl.classList.add('chosen'); bumpCount(); }
saveChosen(); renderTray();
}
- function removeChoice(key) {
- const i = chosen.findIndex(c => c.key === key);
+ function removeChoice(code) {
+ const i = chosen.findIndex(c => c.code === code);
if (i >= 0) chosen.splice(i, 1);
saveChosen(); renderTray();
- const pin = document.querySelector('#pin-layer .el-pin[data-key="' + cssEsc(key) + '"]');
+ const pin = document.querySelector('#pin-layer .el-pin[data-code="' + cssEsc(code) + '"]');
if (pin) pin.classList.remove('chosen');
}
function bumpCount() {
@@ -566,10 +638,12 @@ function boot() {
c.classList.add('bump'); setTimeout(() => c.classList.remove('bump'), 180);
}
function chosenText() {
- // Sort by version order then element number for a tidy composition spec.
+ // Sort by version order then element number for a tidy composition spec. Each row
+ // leads with the unambiguous GLOBAL CODE (1003QHV1) so a composed spec references
+ // the durable cross-build ID, not the old "V1.4" version-local key.
const sorted = chosen.slice().sort((a, b) =>
- (VERSION_ORDER.indexOf(a.version) - VERSION_ORDER.indexOf(b.version)) || (a.n - b.n));
- return sorted.map(c => c.key + ' ' + c.label).join('\n');
+ (VERSION_ORDER.indexOf(a.version) - VERSION_ORDER.indexOf(b.version)) || ((a.n || 0) - (b.n || 0)));
+ return sorted.map(c => (c.code || ('V' + c.version + '.' + c.n)) + ' ' + c.label).join('\n');
}
function renderTray() {
const tray = document.getElementById('chosen-tray'); if (!tray) return;
@@ -588,15 +662,16 @@ function boot() {
if (empty) empty.style.display = 'none';
items.style.display = 'flex';
const sorted = chosen.slice().sort((a, b) =>
- (VERSION_ORDER.indexOf(a.version) - VERSION_ORDER.indexOf(b.version)) || (a.n - b.n));
+ (VERSION_ORDER.indexOf(a.version) - VERSION_ORDER.indexOf(b.version)) || ((a.n || 0) - (b.n || 0)));
sorted.forEach(c => {
+ const code = c.code || ('V' + c.version + '.' + c.n);
const row = document.createElement('div');
row.className = 'ct-chip';
row.innerHTML =
- '<span class="ctc-tag">' + c.key + '</span>' +
+ '<span class="ctc-tag" title="Globally-unique element code">' + escapeHtml(code) + '</span>' +
'<span class="ctc-name">' + escapeHtml(c.label) + '</span>' +
'<button class="ctc-x" title="Remove">×</button>';
- row.querySelector('.ctc-x').addEventListener('click', () => removeChoice(c.key));
+ row.querySelector('.ctc-x').addEventListener('click', () => removeChoice(code));
items.appendChild(row);
});
}
@@ -734,6 +809,21 @@ function boot() {
get overlayOn() { return overlayOn; },
get chosen() { return chosen.slice(); },
chosenText,
+ // chunk-H surface: the global-code machinery, for the proof harness to assert
+ // uniqueness + stability and for any consumer needing a code lookup.
+ BUILD_CODE,
+ codeFor,
+ assignElementCodes,
+ // Flat list of every {version,n,code,label} across all builds — convenient for
+ // the uniqueness assertion.
+ allCodes() {
+ const out = [];
+ VERSION_ORDER.forEach(vk => {
+ const v = VERSIONS[vk];
+ if (v && Array.isArray(v.elements)) v.elements.forEach(e => out.push({ version: vk, n: e.n, code: e.code, label: e.label }));
+ });
+ return out;
+ },
VERSIONS, VERSION_ORDER
};
}
← c4a14bb Slice-2 contrarian fixes: HOLE-3 detail starts COLLAPSED on
·
back to Quadrille Showroom
·
Slice-3 F: FPS.ms.draws HUD readout + off-thread wall-clad d 0979b2b →