chore(embedded): cut v0.0.27-beta
Some checks failed
Notify chart dev on beta cut / notify-chart-dev (push) Failing after 8s

This commit is contained in:
ZDDC 2026-06-10 13:57:51 -05:00
parent e2c2d1571d
commit 8368cc81b8
7 changed files with 109 additions and 19 deletions

View file

@ -2717,7 +2717,7 @@ td[data-field="trackingNumber"] {
</svg> </svg>
<div class="header-title-group"> <div class="header-title-group">
<span class="app-header__title">ZDDC Archive</span> <span class="app-header__title">ZDDC Archive</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:32:03 · 203674e</span></span> <span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15</span></span>
</div> </div>
<button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button> <button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button>
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh Data"></button> <button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh Data"></button>

View file

@ -2824,7 +2824,7 @@ li.CodeMirror-hint-active {
</svg> </svg>
<div class="header-title-group"> <div class="header-title-group">
<span class="app-header__title">ZDDC Browse</span> <span class="app-header__title">ZDDC Browse</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:32:03 · 203674e</span></span> <span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15</span></span>
</div> </div>
<button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button> <button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button>
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh listing" aria-label="Refresh listing"></button> <button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh listing" aria-label="Refresh listing"></button>

View file

@ -1457,7 +1457,12 @@ body.is-elevated::after {
} }
/* ── Welcome screen (intro + tutorial) ─────────────────────────────────── */ /* ── Welcome screen (intro + tutorial) ─────────────────────────────────── */
/* Scroll when the viewport is short. The inner card uses auto margins instead
of the base .empty-state's align-items:center so it centers when it fits but
collapses to the top when taller than the viewport — otherwise centering
clips the top of the card and it can't be scrolled into view. */
.empty-state--overlay { overflow-y: auto; } .empty-state--overlay { overflow-y: auto; }
.empty-state--overlay > .empty-state__inner { margin: auto; }
.welcome { max-width: 900px; padding: 1.5rem 0.5rem 2.5rem; } .welcome { max-width: 900px; padding: 1.5rem 0.5rem 2.5rem; }
.welcome__title { font-size: 2.6rem; line-height: 1.1; margin: 0 0 0.6rem; } .welcome__title { font-size: 2.6rem; line-height: 1.1; margin: 0 0 0.6rem; }
.welcome__lede { .welcome__lede {
@ -1492,7 +1497,8 @@ body.is-elevated::after {
/* ── Workspaces (welcome manager) ──────────────────────────────────────── */ /* ── Workspaces (welcome manager) ──────────────────────────────────────── */
.workspaces { text-align: left; margin: 1.5rem 0 0.5rem; } .workspaces { text-align: left; margin: 1.5rem 0 0.5rem; }
.ws-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; } .ws-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.ws-head__actions { display: flex; gap: 0.5rem; }
.ws-head h2 { margin: 0; font-size: 1.4rem; } .ws-head h2 { margin: 0; font-size: 1.4rem; }
.ws-list { display: flex; flex-direction: column; gap: 0.4rem; } .ws-list { display: flex; flex-direction: column; gap: 0.4rem; }
.ws-empty { color: var(--text-muted); font-size: 0.85rem; padding: 0.75rem; border: 1px dashed var(--border); border-radius: var(--radius); } .ws-empty { color: var(--text-muted); font-size: 0.85rem; padding: 0.75rem; border: 1px dashed var(--border); border-radius: var(--radius); }
@ -2233,7 +2239,7 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o
</svg> </svg>
<div class="header-title-group"> <div class="header-title-group">
<span class="app-header__title">ZDDC Classifier</span> <span class="app-header__title">ZDDC Classifier</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:32:03 · 203674e</span></span> <span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15</span></span>
</div> </div>
<button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button> <button id="addDirectoryBtn" class="btn btn-primary">Use Local Directory</button>
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh and rescan directory" aria-label="Refresh" style="font-size:1.1rem;"></button> <button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh and rescan directory" aria-label="Refresh" style="font-size:1.1rem;"></button>
@ -2369,8 +2375,8 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o
<div class="pane-header-right"> <div class="pane-header-right">
<span id="classifyStats" class="file-stats"></span> <span id="classifyStats" class="file-stats"></span>
<span class="header-divider">|</span> <span class="header-divider">|</span>
<button id="exportDatasetBtn" class="btn btn-secondary btn-sm" title="Export the classification dataset (trees + assignments) as JSON">Export</button> <button id="exportDatasetBtn" class="btn btn-secondary btn-sm" title="Download the classifications as a filename-per-file JSON to edit (e.g. with an AI), then re-import here. NOT a workspace — no scanned tree.">Export for editing</button>
<button id="importDatasetBtn" class="btn btn-secondary btn-sm" title="Import a classification dataset JSON (replaces the current one)">Import</button> <button id="importDatasetBtn" class="btn btn-secondary btn-sm" title="Load an edited classification JSON back in — replaces the current classifications. (To move a whole scanned workspace between browsers, use “Import workspace” on the welcome screen.)">Import edits</button>
<input type="file" id="importDatasetInput" accept="application/json,.json" hidden> <input type="file" id="importDatasetInput" accept="application/json,.json" hidden>
<button id="resetDatasetBtn" class="btn btn-sm btn-danger" title="Discard all classifications and start over from the raw scanned input (does not touch your files)">Reset</button> <button id="resetDatasetBtn" class="btn btn-sm btn-danger" title="Discard all classifications and start over from the raw scanned input (does not touch your files)">Reset</button>
<button id="copyOutputBtn" class="btn btn-primary btn-sm" disabled title="Copy mapped files to an output directory (source untouched)">Copy…</button> <button id="copyOutputBtn" class="btn btn-primary btn-sm" disabled title="Copy mapped files to an output directory (source untouched)">Copy…</button>
@ -2415,7 +2421,11 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o
<section id="workspacesSection" class="workspaces"> <section id="workspacesSection" class="workspaces">
<div class="ws-head"> <div class="ws-head">
<h2>Your workspaces</h2> <h2>Your workspaces</h2>
<button id="newWorkspaceBtn" class="btn btn-primary">+ New workspace</button> <div class="ws-head__actions">
<button id="importWorkspaceBtn" class="btn btn-secondary" title="Import a whole workspace (.zddc-workspace.json) exported from another browser — restores the scanned snapshot so you don't re-scan">⭱ Import workspace</button>
<button id="newWorkspaceBtn" class="btn btn-primary">+ New workspace</button>
</div>
<input type="file" id="importWorkspaceInput" accept="application/json,.json" hidden>
</div> </div>
<div id="workspaceList" class="ws-list"><!-- rendered --></div> <div id="workspaceList" class="ws-list"><!-- rendered --></div>
</section> </section>
@ -5883,7 +5893,7 @@ X.B(E,Y);return E}return J}())
var url = URL.createObjectURL(blob); var url = URL.createObjectURL(blob);
var a = document.createElement('a'); var a = document.createElement('a');
a.href = url; a.href = url;
a.download = String(name).replace(/[^\w.-]+/g, '_') + '.json'; a.download = String(name).replace(/[^\w.-]+/g, '_') + '.zddc-classification.json';
document.body.appendChild(a); a.click(); a.remove(); document.body.appendChild(a); a.click(); a.remove();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
@ -7796,6 +7806,8 @@ X.B(E,Y);return E}return J}())
newBtn: document.getElementById('newWorkspaceBtn'), newBtn: document.getElementById('newWorkspaceBtn'),
wsBtn: document.getElementById('workspacesBtn'), wsBtn: document.getElementById('workspacesBtn'),
connectBtn: document.getElementById('connectDirBtn'), connectBtn: document.getElementById('connectDirBtn'),
importBtn: document.getElementById('importWorkspaceBtn'),
importInput: document.getElementById('importWorkspaceInput'),
}; };
if (!P() || !P().available) { if (!P() || !P().available) {
// No IndexedDB → hide the workspace UI; legacy rename path still works. // No IndexedDB → hide the workspace UI; legacy rename path still works.
@ -7807,6 +7819,11 @@ X.B(E,Y);return E}return J}())
if (els.wsBtn) els.wsBtn.addEventListener('click', showWelcome); if (els.wsBtn) els.wsBtn.addEventListener('click', showWelcome);
if (els.connectBtn) els.connectBtn.addEventListener('click', function () { tryReconnect(false); }); if (els.connectBtn) els.connectBtn.addEventListener('click', function () { tryReconnect(false); });
if (els.list) els.list.addEventListener('click', onListClick); if (els.list) els.list.addEventListener('click', onListClick);
if (els.importBtn) els.importBtn.addEventListener('click', function () { els.importInput.click(); });
if (els.importInput) els.importInput.addEventListener('change', function () {
if (this.files && this.files[0]) importWorkspace(this.files[0]);
this.value = '';
});
// Autosave the active workspace whenever the map changes. // Autosave the active workspace whenever the map changes.
C().on(scheduleAutosave); C().on(scheduleAutosave);
@ -7862,10 +7879,15 @@ X.B(E,Y);return E}return J}())
var actions = document.createElement('div'); var actions = document.createElement('div');
actions.className = 'ws-row__actions'; actions.className = 'ws-row__actions';
[['open', 'Open'], ['rename', 'Rename'], ['delete', 'Delete']].forEach(function (a) { var titles = {
export: 'Export this whole workspace (scanned snapshot + classifications) to transfer to another browser or back up',
delete: 'Delete this workspace — your source files are untouched',
};
[['open', 'Open'], ['rename', 'Rename'], ['export', 'Export'], ['delete', 'Delete']].forEach(function (a) {
var b = document.createElement('button'); var b = document.createElement('button');
b.className = 'btn btn-sm ' + (a[0] === 'open' ? 'btn-primary' : 'btn-secondary'); b.className = 'btn btn-sm ' + (a[0] === 'open' ? 'btn-primary' : 'btn-secondary');
b.dataset.act = a[0]; b.textContent = a[1]; b.dataset.act = a[0]; b.textContent = a[1];
if (titles[a[0]]) b.title = titles[a[0]];
actions.appendChild(b); actions.appendChild(b);
}); });
row.appendChild(main); row.appendChild(actions); row.appendChild(main); row.appendChild(actions);
@ -7879,9 +7901,74 @@ X.B(E,Y);return E}return J}())
if (!id) return; if (!id) return;
if (btn.dataset.act === 'open') openWorkspace(id); if (btn.dataset.act === 'open') openWorkspace(id);
else if (btn.dataset.act === 'rename') renameWorkspace(id); else if (btn.dataset.act === 'rename') renameWorkspace(id);
else if (btn.dataset.act === 'export') exportWorkspace(id);
else if (btn.dataset.act === 'delete') deleteWorkspace(id); else if (btn.dataset.act === 'delete') deleteWorkspace(id);
} }
// ── transfer (export / import a whole workspace as JSON) ─────────────────
// The scan is the expensive part (minutes on cloud mounts), so a workspace
// is portable: its snapshot + classify map travel as one JSON. The source
// directory HANDLE can't cross browsers, so an imported workspace has none —
// "Connect directory" re-attaches the folder once, without re-scanning.
async function exportWorkspace(id) {
var rows = await P().listWorkspaces();
var meta = rows.filter(function (r) { return r.id === id; })[0];
var rec = await P().getWorkspace(id);
if (!meta || !rec) { window.zddc.toast('Could not load that workspace to export.', 'error'); return; }
var payload = {
zddcWorkspace: 1,
exportedAt: new Date().toISOString(),
meta: { name: meta.name, rootName: meta.rootName, createdAt: meta.createdAt, updatedAt: meta.updatedAt, summary: meta.summary },
tree: rec.tree || [], // the scanned snapshot (no re-scan on the other side)
classify: rec.classify || {}, // assignments + target trees
};
var blob = new Blob([JSON.stringify(payload)], { type: 'application/json' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = String(meta.name || 'workspace').replace(/[^\w.-]+/g, '_') + '.zddc-workspace.json';
document.body.appendChild(a); a.click(); a.remove();
URL.revokeObjectURL(url);
window.zddc.toast('Exported “' + meta.name + '”. Import it in another browser to skip the re-scan.', 'success');
}
function importWorkspace(file) {
return new Promise(function (resolve) {
var reader = new FileReader();
reader.onload = function () {
var obj;
try { obj = JSON.parse(reader.result); }
catch (e) { window.zddc.toast('Import failed — not valid JSON.', 'error'); resolve(null); return; }
if (!obj || !obj.zddcWorkspace) {
window.zddc.toast(obj && obj.zddcClassifierFiles
? 'Thats a classification dataset, not a workspace — open a workspace and use Import in the toolbar.'
: 'Import failed — not a ZDDC workspace export.', 'error');
resolve(null); return;
}
var m = obj.meta || {};
var id = uid();
var meta = {
id: id,
name: m.name || 'Imported workspace',
rootName: m.rootName || '',
createdAt: m.createdAt || now(),
updatedAt: now(),
summary: m.summary || { files: 0, done: 0, excluded: 0 },
};
// No rootHandle — it can't be serialized across browsers; the
// user re-attaches via "Connect directory" after opening.
P().putWorkspace(meta, { id: id, rootHandle: null, tree: obj.tree || [], classify: obj.classify || {} })
.then(function () {
renderList();
window.zddc.toast('Imported “' + meta.name + '”. Open it, then “Connect directory” to re-attach the source folder for preview/copy.', 'success', { durationMs: 8000 });
resolve(id);
});
};
reader.onerror = function () { window.zddc.toast('Import failed — could not read the file.', 'error'); resolve(null); };
reader.readAsText(file);
});
}
// ── summary ─────────────────────────────────────────────────────────── // ── summary ───────────────────────────────────────────────────────────
function allFiles() { function allFiles() {
var out = []; var out = [];
@ -8058,9 +8145,12 @@ X.B(E,Y);return E}return J}())
showWelcome: showWelcome, showWelcome: showWelcome,
newWorkspace: newWorkspace, newWorkspace: newWorkspace,
openWorkspace: openWorkspace, openWorkspace: openWorkspace,
exportWorkspace: exportWorkspace,
importWorkspace: importWorkspace,
onRescanned: onRescanned, onRescanned: onRescanned,
renderList: renderList, renderList: renderList,
activeId: function () { return activeId; }, activeId: function () { return activeId; },
activeName: function () { return activeMeta ? activeMeta.name : null; },
}; };
})(); })();

View file

@ -1671,7 +1671,7 @@ body {
</svg> </svg>
<div class="header-title-group"> <div class="header-title-group">
<span class="app-header__title">ZDDC</span> <span class="app-header__title">ZDDC</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:32:03 · 203674e</span></span> <span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15</span></span>
</div> </div>
</div> </div>
<div class="header-right"> <div class="header-right">

View file

@ -2770,7 +2770,7 @@ dialog.modal--narrow {
</svg> </svg>
<div class="header-title-group"> <div class="header-title-group">
<span class="app-header__title">ZDDC Transmittal</span> <span class="app-header__title">ZDDC Transmittal</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:32:03 · 203674e</span></span> <span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:57:43 · e2c2d15</span></span>
</div> </div>
<span id="no-js-notice" class="text-gray-400 text-xs italic">JavaScript not available</span> <span id="no-js-notice" class="text-gray-400 text-xs italic">JavaScript not available</span>
<!-- Publish split-button (Transmittal-specific primary action; <!-- Publish split-button (Transmittal-specific primary action;

View file

@ -1,8 +1,8 @@
# Generated by build.sh — do not edit. One <app>=<build label> per line. # Generated by build.sh — do not edit. One <app>=<build label> per line.
archive=v0.0.27-beta · 2026-06-10 18:32:03 · 203674e archive=v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15
transmittal=v0.0.27-beta · 2026-06-10 18:32:03 · 203674e transmittal=v0.0.27-beta · 2026-06-10 18:57:43 · e2c2d15
classifier=v0.0.27-beta · 2026-06-10 18:32:03 · 203674e classifier=v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15
landing=v0.0.27-beta · 2026-06-10 18:32:03 · 203674e landing=v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15
form=v0.0.27-beta · 2026-06-10 18:32:03 · 203674e form=v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15
tables=v0.0.27-beta · 2026-06-10 18:32:03 · 203674e tables=v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15
browse=v0.0.27-beta · 2026-06-10 18:32:03 · 203674e browse=v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15

View file

@ -1722,7 +1722,7 @@ body.is-elevated::after {
</svg> </svg>
<div class="header-title-group"> <div class="header-title-group">
<span class="app-header__title" id="table-title">ZDDC Table</span> <span class="app-header__title" id="table-title">ZDDC Table</span>
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:32:03 · 203674e</span></span> <span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.27-beta · 2026-06-10 18:57:44 · e2c2d15</span></span>
</div> </div>
</div> </div>
<div class="header-right"> <div class="header-right">