Compare commits

..

2 commits

Author SHA1 Message Date
de046360e6 release: v0.0.24 lockstep
Some checks failed
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 8s
Build + deploy releases / build-and-deploy (push) Successful in 21s
Build + deploy releases / notify-chart-prod (push) Failing after 7s
2026-05-22 11:11:36 -05:00
fab44542bc fix(archive): normalize trailing slash in multi-project server listing
GET / Accept:application/json changed shape in the May-2026 reshape:
it returns listing.FileInfo entries (directory names carry a trailing
'/', and the array can include non-directory entries) instead of the
legacy ProjectInfo array (bare names). archive.html's multi-project
mode (?projects=A,B) intersected those server names against the
projectFilter parsed from the URL — which is slash-free — so every
listed project missed the intersection, projectFilter emptied, the
"you don't have access" banner showed, and nothing scanned: empty
projects dropdown, no parties/transmittal folders.

Normalise serverNames (and the projectTitles keys) to bare directory
names and filter the listing to is_dir entries before intersecting.
The scan in source.js already uses the slash-free projectFilter
directly, so this single normalization restores the whole flow.

Verified headless against a 2-project fixture, old vs new binary:
old -> projectFilter [], no-access warning, no parties rendered;
new -> projectFilter [182246,197072], no warning, ACME/BETACO parties
rendered. Reaches prod via the next zddc-server release (archive.html
is //go:embed'd).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:59:24 -05:00
9 changed files with 49 additions and 27 deletions

View file

@ -138,11 +138,22 @@
var serverProjects = await resp.json(); var serverProjects = await resp.json();
if (Array.isArray(serverProjects) && serverProjects.length > 0 if (Array.isArray(serverProjects) && serverProjects.length > 0
&& serverProjects[0] && typeof serverProjects[0].name === 'string') { && serverProjects[0] && typeof serverProjects[0].name === 'string') {
serverNames = new Set(serverProjects.map(function(p) { return p.name; })); // GET / Accept: application/json returns listing.FileInfo
// entries (not the legacy ProjectInfo shape): directory
// names carry a trailing "/", and the listing can include
// non-directory entries. Normalise to bare directory names
// so they match the slash-free projectFilter parsed from
// ?projects= (url-state.js). Without this, every URL-listed
// project misses the intersection below → "no access"
// banner + empty scan.
var bareName = function (p) { return p.name.replace(/\/+$/, ''); };
var isProjectDir = function (p) { return p.is_dir === true || /\/$/.test(p.name); };
var projectEntries = serverProjects.filter(isProjectDir);
serverNames = new Set(projectEntries.map(bareName));
var titles = {}; var titles = {};
serverProjects.forEach(function (p) { projectEntries.forEach(function (p) {
if (p && typeof p.title === 'string' && p.title) { if (p && typeof p.title === 'string' && p.title) {
titles[p.name] = p.title; titles[bareName(p)] = p.title;
} }
}); });
window.app.projectTitles = titles; window.app.projectTitles = titles;

View file

@ -402,10 +402,10 @@
} }
async function scanHttpRoot(scanRootUrl, rootUrl, callbacks) { async function scanHttpRoot(scanRootUrl, rootUrl, callbacks) {
// Mode 1 — multi-project (?projects= set). Skip listing scanRootUrl entirely: // Mode 1 — multi-project (?projects= set). Skip listing scanRootUrl
// the zddc-server returns a ProjectInfo array there (not a Caddy fileInfo // entirely: project URLs are deterministic, so go straight to each one
// listing), so iterating it as if it were a directory listing wouldn't work. // (the names in projectFilter, slash-normalised in app.js against the
// Project URLs are deterministic — go straight to each one. // server's root listing). Avoids depending on the root listing's shape.
if (window.app.projectFilter && window.app.projectFilter.size > 0) { if (window.app.projectFilter && window.app.projectFilter.size > 0) {
const tasks = []; const tasks = [];
for (const name of window.app.projectFilter) { for (const name of window.app.projectFilter) {

View file

@ -2582,7 +2582,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">v0.0.23</span> <span class="build-timestamp">v0.0.24</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>
@ -6193,10 +6193,10 @@ X.B(E,Y);return E}return J}())
} }
async function scanHttpRoot(scanRootUrl, rootUrl, callbacks) { async function scanHttpRoot(scanRootUrl, rootUrl, callbacks) {
// Mode 1 — multi-project (?projects= set). Skip listing scanRootUrl entirely: // Mode 1 — multi-project (?projects= set). Skip listing scanRootUrl
// the zddc-server returns a ProjectInfo array there (not a Caddy fileInfo // entirely: project URLs are deterministic, so go straight to each one
// listing), so iterating it as if it were a directory listing wouldn't work. // (the names in projectFilter, slash-normalised in app.js against the
// Project URLs are deterministic — go straight to each one. // server's root listing). Avoids depending on the root listing's shape.
if (window.app.projectFilter && window.app.projectFilter.size > 0) { if (window.app.projectFilter && window.app.projectFilter.size > 0) {
const tasks = []; const tasks = [];
for (const name of window.app.projectFilter) { for (const name of window.app.projectFilter) {
@ -9818,11 +9818,22 @@ window.app.modules.filtering = {
var serverProjects = await resp.json(); var serverProjects = await resp.json();
if (Array.isArray(serverProjects) && serverProjects.length > 0 if (Array.isArray(serverProjects) && serverProjects.length > 0
&& serverProjects[0] && typeof serverProjects[0].name === 'string') { && serverProjects[0] && typeof serverProjects[0].name === 'string') {
serverNames = new Set(serverProjects.map(function(p) { return p.name; })); // GET / Accept: application/json returns listing.FileInfo
// entries (not the legacy ProjectInfo shape): directory
// names carry a trailing "/", and the listing can include
// non-directory entries. Normalise to bare directory names
// so they match the slash-free projectFilter parsed from
// ?projects= (url-state.js). Without this, every URL-listed
// project misses the intersection below → "no access"
// banner + empty scan.
var bareName = function (p) { return p.name.replace(/\/+$/, ''); };
var isProjectDir = function (p) { return p.is_dir === true || /\/$/.test(p.name); };
var projectEntries = serverProjects.filter(isProjectDir);
serverNames = new Set(projectEntries.map(bareName));
var titles = {}; var titles = {};
serverProjects.forEach(function (p) { projectEntries.forEach(function (p) {
if (p && typeof p.title === 'string' && p.title) { if (p && typeof p.title === 'string' && p.title) {
titles[p.name] = p.title; titles[bareName(p)] = p.title;
} }
}); });
window.app.projectTitles = titles; window.app.projectTitles = titles;

View file

@ -2344,7 +2344,7 @@ body {
</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">v0.0.23</span> <span class="build-timestamp">v0.0.24</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

@ -1793,7 +1793,7 @@ body.is-elevated::after {
</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">v0.0.23</span> <span class="build-timestamp">v0.0.24</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>

View file

@ -1536,7 +1536,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">v0.0.23</span> <span class="build-timestamp">v0.0.24</span>
</div> </div>
</div> </div>
<div class="header-right"> <div class="header-right">

View file

@ -2635,7 +2635,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">v0.0.23</span> <span class="build-timestamp">v0.0.24</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.23 archive=v0.0.24
transmittal=v0.0.23 transmittal=v0.0.24
classifier=v0.0.23 classifier=v0.0.24
landing=v0.0.23 landing=v0.0.24
form=v0.0.23 form=v0.0.24
tables=v0.0.23 tables=v0.0.24
browse=v0.0.23 browse=v0.0.24

View file

@ -1534,7 +1534,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">v0.0.23</span> <span class="build-timestamp">v0.0.24</span>
</div> </div>
</div> </div>
<div class="header-right"> <div class="header-right">