From 4cd39998aa7a2d694e167541237df748e42844de Mon Sep 17 00:00:00 2001 From: ZDDC Date: Sun, 10 May 2026 07:57:40 -0500 Subject: [PATCH] chore(embedded): cut v0.0.17-beta --- zddc/internal/apps/embedded/archive.html | 2 +- zddc/internal/apps/embedded/browse.html | 2 +- zddc/internal/apps/embedded/classifier.html | 2 +- zddc/internal/apps/embedded/index.html | 346 ++++++++++++++++++- zddc/internal/apps/embedded/mdedit.html | 2 +- zddc/internal/apps/embedded/transmittal.html | 2 +- zddc/internal/apps/embedded/versions.txt | 16 +- zddc/internal/handler/tables.html | 2 +- 8 files changed, 358 insertions(+), 16 deletions(-) diff --git a/zddc/internal/apps/embedded/archive.html b/zddc/internal/apps/embedded/archive.html index afa3e4b..2820815 100644 --- a/zddc/internal/apps/embedded/archive.html +++ b/zddc/internal/apps/embedded/archive.html @@ -2267,7 +2267,7 @@ td[data-field="trackingNumber"] {
ZDDC Archive - v0.0.17-beta · 2026-05-10 · iron-ribbon-crystal + v0.0.17-beta · 2026-05-10 · log-citrus-hazel
diff --git a/zddc/internal/apps/embedded/browse.html b/zddc/internal/apps/embedded/browse.html index a4bb7b0..34bb86b 100644 --- a/zddc/internal/apps/embedded/browse.html +++ b/zddc/internal/apps/embedded/browse.html @@ -969,7 +969,7 @@ body {
ZDDC Browse - v0.0.17-beta · 2026-05-10 · iron-ribbon-crystal + v0.0.17-beta · 2026-05-10 · log-citrus-hazel
diff --git a/zddc/internal/apps/embedded/classifier.html b/zddc/internal/apps/embedded/classifier.html index 08a0b30..e8421e2 100644 --- a/zddc/internal/apps/embedded/classifier.html +++ b/zddc/internal/apps/embedded/classifier.html @@ -1486,7 +1486,7 @@ body.help-open .app-header {
ZDDC Classifier - v0.0.17-beta · 2026-05-10 · iron-ribbon-crystal + v0.0.17-beta · 2026-05-10 · log-citrus-hazel
diff --git a/zddc/internal/apps/embedded/index.html b/zddc/internal/apps/embedded/index.html index 29d3bad..0581ee4 100644 --- a/zddc/internal/apps/embedded/index.html +++ b/zddc/internal/apps/embedded/index.html @@ -995,6 +995,128 @@ body { color: var(--text-muted); } +/* ── Project mode ──────────────────────────────────────────────────────── */ +/* Activated when location.pathname is a single project segment (e.g. + /Project-1). Picker UI is hidden; this block surfaces the four + lifecycle-stage cards and MDL editing instructions. */ + +.project-title { + font-size: 1.6rem; + margin: 0 0 0.25rem; + font-weight: 600; +} + +.project-title__subtle { + color: var(--text-muted); + font-weight: normal; + font-size: 0.9rem; +} + +.lead { + color: var(--text-muted); + margin: 0.25rem 0 1.5rem; +} + +.stages { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 0.85rem; + margin: 1rem 0 1.5rem; +} + +.stage-card { + display: block; + padding: 1rem 1.1rem; + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius); + text-decoration: none; + color: var(--text); + transition: border-color 0.15s, box-shadow 0.15s, transform 0.05s; + cursor: pointer; +} + +.stage-card:hover { + border-color: var(--primary); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + +.stage-card:active { + transform: translateY(1px); +} + +.stage-card h3 { + margin: 0 0 0.3rem; + font-size: 1rem; + color: var(--primary); + font-weight: 600; +} + +.stage-card p { + margin: 0; + color: var(--text-muted); + font-size: 0.875rem; +} + +.browse-link { + display: inline-block; + margin-top: 0.25rem; + color: var(--primary); + text-decoration: none; + cursor: pointer; +} + +.browse-link:hover { + text-decoration: underline; +} + +#projectView ol { + padding-left: 1.5rem; +} + +#projectView ol li { + margin-bottom: 0.4rem; +} + +#projectView code { + font-family: var(--font-mono); + background: var(--bg-secondary); + padding: 0.1em 0.35em; + border-radius: 3px; + font-size: 0.86em; +} + +#projectView h2 { + font-size: 1.1rem; + margin: 2.25rem 0 0.5rem; + padding-bottom: 0.3rem; + border-bottom: 1px solid var(--border); + font-weight: 600; +} + +.party-list { + padding-left: 1.5rem; + margin: 0.4rem 0 1rem; +} + +.party-list li { + margin-bottom: 0.25rem; +} + +.party-list a { + color: var(--primary); + text-decoration: none; +} + +.party-list a:hover { + text-decoration: underline; +} + +.party-list-none-yet { + color: var(--text-muted); + font-style: italic; +} + @@ -1010,7 +1132,7 @@ body {
ZDDC - v0.0.17-beta · 2026-05-10 · iron-ribbon-crystal + v0.0.17-beta · 2026-05-10 · log-citrus-hazel
@@ -1019,7 +1141,9 @@ body {
-
+
+ +

Welcome to the ZDDC Archive

@@ -1078,6 +1202,67 @@ body {
Loading projects…
+ + + +
@@ -2707,9 +2892,163 @@ body { catch (e) { /* private mode / quota */ } } + // ── Project mode ───────────────────────────────────────────────────────── + // + // The same landing tool serves at / as the project-workspace + // page. Mode is determined from location.pathname: + // + // / → 'picker' (existing behavior) + // / → 'project' + // /index.html → 'picker' (file:// + standalone-served root) + // anything else → 'picker' (best-effort fallback) + // + // Project mode shows the four canonical lifecycle-stage cards, a + // "browse all files" link, and a Master Deliverables List section + // with direct links to any parties currently in archive/. The party + // list is fetched from //?json=1; failures fall + // back to the static "no parties yet" copy. + + function detectMode() { + if (typeof location === 'undefined') return 'picker'; + var path = location.pathname || '/'; + // Strip any trailing /index.html so the deployment-root case + // matches even on file:// or behind some servers. + var trimmed = path.replace(/\/index\.html$/, '/'); + if (trimmed === '' || trimmed === '/') return 'picker'; + // Single non-slash, non-dot segment → project root. + var parts = trimmed.split('/').filter(Boolean); + if (parts.length === 1 && parts[0].indexOf('.') === -1) { + return 'project'; + } + return 'picker'; + } + + function projectFromPath() { + var parts = (location.pathname || '/').split('/').filter(Boolean); + return parts[0] || ''; + } + + // Render the project-workspace view: title, four stage links, MDL + // section. Stage hrefs use the no-trailing-slash form so the server + // routes them to each canonical default tool (mdedit for working/, + // transmittal for staging/, etc.). Browse-all and the archive deep + // link use the slash form to land on the directory listing. + async function renderProjectMode() { + var project = projectFromPath(); + if (!project) return; + + // Hide picker, show project view. + var picker = document.getElementById('pickerView'); + var projectView = document.getElementById('projectView'); + if (picker) picker.classList.add('hidden'); + if (projectView) projectView.classList.remove('hidden'); + + document.title = project + ' — ZDDC'; + var titleEl = document.getElementById('projectName'); + if (titleEl) titleEl.textContent = project; + + var p = encodeURIComponent(project); + var stages = [ + { id: 'stageArchive', href: '/' + p + '/archive' }, + { id: 'stageWorking', href: '/' + p + '/working' }, + { id: 'stageStaging', href: '/' + p + '/staging' }, + { id: 'stageReviewing', href: '/' + p + '/reviewing' }, + ]; + for (var i = 0; i < stages.length; i++) { + var a = document.getElementById(stages[i].id); + if (a) a.setAttribute('href', stages[i].href); + } + + var browseAll = document.getElementById('browseAllLink'); + if (browseAll) { + browseAll.setAttribute('href', '/' + p + '/'); + browseAll.textContent = 'Browse all files →'; + } + var archiveBrowse = document.getElementById('archiveBrowseLink'); + if (archiveBrowse) { + archiveBrowse.setAttribute('href', '/' + p + '/archive/'); + archiveBrowse.innerHTML = '/' + escapeHtml(project) + '/archive/'; + } + + // Fetch party list. Best-effort — failures render the + // no-parties-yet fallback. We try //archive/ — the + // server returns the listing in either lowercase or PascalCase + // form; either yields the same JSON shape via case-insensitive + // URL canonicalization. + var partySection = document.getElementById('partyListSection'); + if (!partySection) return; + + var parties = await fetchParties(p); + if (parties == null) { + // Network error or unauthenticated — show neither list nor + // explicit "none" message. The page is still usable. + partySection.innerHTML = ''; + return; + } + if (parties.length === 0) { + partySection.innerHTML = + '

No party folders yet. The MDL view auto-renders at any ' + + 'archive/<party>/mdl/ URL, even when the folder doesn\'t exist on ' + + 'disk — so you can start editing an MDL before any transmittals have been exchanged.

'; + return; + } + var html = '

Direct links — parties currently in archive/:

' + + '
    '; + for (var j = 0; j < parties.length; j++) { + var name = parties[j].name; + var url = parties[j].url; // server-provided absolute URL + html += '
  • ' + escapeHtml(name) + ' MDL →
  • '; + } + html += '
'; + partySection.innerHTML = html; + } + + // Returns an array of {name, url} for each party folder in the + // project's archive/, sorted by name. Returns null if the listing + // can't be fetched (offline, 4xx, or non-JSON response). Returns + // [] if the listing succeeds but archive/ is empty / has no + // visible party folders. + async function fetchParties(projectURL) { + try { + var resp = await fetch('/' + projectURL + '/archive/', { + headers: { 'Accept': 'application/json' }, + cache: 'no-cache', + credentials: 'same-origin' + }); + if (!resp.ok) return null; + var ctype = resp.headers.get('Content-Type') || ''; + if (!ctype.toLowerCase().includes('json')) return null; + var data = await resp.json(); + if (!Array.isArray(data)) return null; + // Server emits directories with trailing "/" on the name. + // Filter to dirs only, strip the slash for display. + var out = []; + for (var i = 0; i < data.length; i++) { + var e = data[i]; + if (!e.is_dir) continue; + var nm = String(e.name || '').replace(/\/$/, ''); + if (!nm) continue; + if (nm.charAt(0) === '.' || nm.charAt(0) === '_') continue; + out.push({ name: nm, url: e.url || ('/' + projectURL + '/archive/' + encodeURIComponent(nm) + '/') }); + } + out.sort(function (a, b) { return a.name < b.name ? -1 : a.name > b.name ? 1 : 0; }); + return out; + } catch (e) { + return null; + } + } + // ── Bootstrap ──────────────────────────────────────────────────────────── async function init() { + if (detectMode() === 'project') { + await renderProjectMode(); + return; + } + await initPicker(); + } + + async function initPicker() { loadGroups(); urlRestore(); @@ -2764,6 +3103,9 @@ body { saveGroup: saveGroup, openSelectedVisible: openSelectedVisible, dismissWarning: dismissWarning, + // Project-mode entry points (also tested directly). + detectMode: detectMode, + renderProjectMode: renderProjectMode, // Test-only: override the navigation function (avoids the messy // browser-locked-down state of window.location). _setNavigate: function(fn) { navigate = fn; } diff --git a/zddc/internal/apps/embedded/mdedit.html b/zddc/internal/apps/embedded/mdedit.html index 18248ad..766e292 100644 --- a/zddc/internal/apps/embedded/mdedit.html +++ b/zddc/internal/apps/embedded/mdedit.html @@ -1925,7 +1925,7 @@ body.help-open .app-header {
ZDDC Markdown - v0.0.17-beta · 2026-05-10 · iron-ribbon-crystal + v0.0.17-beta · 2026-05-10 · log-citrus-hazel
diff --git a/zddc/internal/apps/embedded/transmittal.html b/zddc/internal/apps/embedded/transmittal.html index b7e558c..7cc8d70 100644 --- a/zddc/internal/apps/embedded/transmittal.html +++ b/zddc/internal/apps/embedded/transmittal.html @@ -2285,7 +2285,7 @@ dialog.modal--narrow {
ZDDC Transmittal - v0.0.17-beta · 2026-05-10 · iron-ribbon-crystal + v0.0.17-beta · 2026-05-10 · log-citrus-hazel
JavaScript not available