@@ -11549,10 +11549,10 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
return;
}
- // Derive the party from the path: archive//incoming//.
+ // Derive the party from the path: incoming///.
var parts = url.replace(/^\/+|\/+$/g, '').split('/');
- var partyIdx = parts.indexOf('archive');
- var party = (partyIdx >= 0 && parts[partyIdx + 1]) ? parts[partyIdx + 1] : '';
+ var incIdx = parts.indexOf('incoming');
+ var party = (incIdx >= 0 && parts[incIdx + 1]) ? parts[incIdx + 1] : '';
var classification = classifyChildren(node, parsedFolder.trackingNumber);
@@ -11607,22 +11607,19 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
// stage.js — Stage and Unstage workflow modals.
//
-// After the layout reshape, working/ and staging/ live INSIDE each
-// party folder: archive//working// and
-// archive//staging//. Stage and Unstage are now
-// per-party — the destination batch is always inside the SAME
-// party's staging slot. The party context is read from the source
-// file's path.
+// In the flat-peer layout working/ and staging/ are top-level peers,
+// each partitioned by party: working// and
+// staging///. Stage and Unstage are per-party — the
+// destination batch is always inside the SAME party's staging peer. The
+// party context is read from the source file's path.
//
-// Stage: move a file from archive//working/<…> into a
-// transmittal folder under archive//staging/<…>. Modal lists
-// existing transmittal folders in the party's staging/ plus a "New
-// transmittal folder…" option that prompts for a ZDDC-conforming
-// name and mkdirs it before the move.
+// Stage: move a file from working//<…> into a transmittal folder
+// under staging//<…>. Modal lists existing transmittal folders in
+// the party's staging/ plus a "New transmittal folder…" option that
+// prompts for a ZDDC-conforming name and mkdirs it before the move.
//
-// Unstage: move a file from archive//staging//
-// back to the user's archive//working// home
-// (overridable).
+// Unstage: move a file from staging/// back to
+// working// (overridable).
//
// Both reuse the existing X-ZDDC-Op: move primitive — no new composite
// endpoint is needed; the client just orchestrates one POST per file
@@ -11643,19 +11640,21 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
// ── Scope detection: path-shape, not cascade-content ──────────────
// A file is stageable if its path matches
- // //archive//working/<…>. Unstageable if it
- // matches //archive//staging//<…>.
- // Both are path-shape queries — content/ACL is enforced server-
- // side.
+ // //working//<…>. Unstageable if it matches
+ // //staging///<…>. Both are path-shape
+ // queries — content/ACL is enforced server-side.
- // projectPartySlot returns { project, party, slot, rest } when
- // path matches //archive///, or
+ var WORKSPACE_PEERS = { working: 1, staging: 1, reviewing: 1, incoming: 1 };
+
+ // projectPartySlot returns { project, party, slot, rest } when path
+ // matches //// for a workspace peer, or
// null on non-match.
function projectPartySlot(path) {
var rel = path.replace(/^\/+|\/+$/g, '').split('/');
- if (rel.length < 4) return null;
- if (rel[1].toLowerCase() !== 'archive') return null;
- return { project: rel[0], party: rel[2], slot: rel[3], rest: rel.slice(4) };
+ if (rel.length < 3) return null;
+ var slot = rel[1].toLowerCase();
+ if (!WORKSPACE_PEERS[slot]) return null;
+ return { project: rel[0], slot: slot, party: rel[2], rest: rel.slice(3) };
}
function isStageableFile(node) {
@@ -11692,7 +11691,7 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
async function fetchStagingFolders(project, party) {
var entries = await listDir(
- '/' + project + '/archive/' + encodeURIComponent(party) + '/staging/');
+ '/' + project + '/staging/' + encodeURIComponent(party) + '/');
return entries
.filter(function (e) { return e && e.isDir; })
.map(function (e) { return e.name; });
@@ -11880,11 +11879,11 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
var srcUrl = tree.pathFor(node);
var info = projectPartySlot(srcUrl);
if (!info || info.slot !== 'working') {
- status('Stage applies only to files under archive//working/.', 'error');
+ status('Stage applies only to files under working//.', 'error');
return;
}
- var stagingBase = '/' + info.project + '/archive/' +
- encodeURIComponent(info.party) + '/staging/';
+ var stagingBase = '/' + info.project + '/staging/' +
+ encodeURIComponent(info.party) + '/';
var folders;
try { folders = await fetchStagingFolders(info.project, info.party); }
catch (e) {
@@ -11922,12 +11921,11 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
var srcUrl = tree.pathFor(node);
var info = projectPartySlot(srcUrl);
if (!info || info.slot !== 'staging') {
- status('Unstage applies only to files under archive//staging/.', 'error');
+ status('Unstage applies only to files under staging//.', 'error');
return;
}
- var email = await fetchSelfEmail();
- var defaultTarget = '/' + info.project + '/archive/' +
- encodeURIComponent(info.party) + '/working/' + (email || '') + '/';
+ var defaultTarget = '/' + info.project + '/working/' +
+ encodeURIComponent(info.party) + '/';
var choice;
try {
choice = await openUnstagePicker({ fileCount: 1, defaultTarget: defaultTarget });
@@ -13168,49 +13166,36 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
// (^[A-Za-z0-9][A-Za-z0-9.-]*$).
function validPartyName(s) { return /^[A-Za-z0-9][A-Za-z0-9.-]*$/.test(s || ''); }
- // The project-level folder-nav aggregators. These have no physical
- // presence: // lists the parties whose
- // archive/// has content. Creating something here means
- // creating it under a party — see createInAggregator.
- var FOLDER_NAV_SLOTS = { working: 1, staging: 1, reviewing: 1 };
+ // The party-partitioned workspace peers. Each is a physical top-level
+ // directory // whose children are / folders.
+ // Creating something at a peer root means choosing a party — see
+ // createInAggregator. (mdl/rsk rows are created via the tables tool;
+ // archive is the WORM record; ssr is the flat registry — none of those
+ // use this picker.)
+ var PARTY_PEERS = { incoming: 1, working: 1, staging: 1, reviewing: 1 };
- // aggregatorRoot returns { project, slot } when parentDir is a
- // project-level folder-nav aggregator root (server mode only), else
- // null. parentDir is a "///" URL.
+ // aggregatorRoot returns { project, slot } when parentDir is a party-
+ // partitioned peer root (server mode only), else null. parentDir is a
+ // "///" URL.
function aggregatorRoot(parentDir) {
if (state.source !== 'server') return null;
var segs = (parentDir || '').replace(/^\/+|\/+$/g, '').split('/');
if (segs.length !== 2 || !segs[0]) return null;
- var slot = segs[1].toLowerCase();
- return FOLDER_NAV_SLOTS[slot] ? { project: segs[0], slot: slot } : null;
+ var peer = segs[1].toLowerCase();
+ return PARTY_PEERS[peer] ? { project: segs[0], slot: peer } : null;
}
- // rewriteAggregatorPath maps a path UNDER a folder-nav aggregator
- // (a party already chosen — ///[/]) to its
- // canonical archive path //archive//[/],
- // mirroring the server's folder-nav redirect. Returns null when
- // parentDir isn't under such an aggregator (root case is handled by
- // aggregatorRoot + the picker). Covers right-clicking a party row
- // shown in an aggregator listing so "New folder" doesn't 409.
- function rewriteAggregatorPath(parentDir) {
- if (state.source !== 'server') return null;
- var segs = (parentDir || '').replace(/^\/+|\/+$/g, '').split('/');
- if (segs.length < 3 || !segs[0]) return null;
- var slot = segs[1].toLowerCase();
- if (!FOLDER_NAV_SLOTS[slot]) return null;
- var p = '/' + segs[0] + '/archive/' + segs[2] + '/' + slot + '/';
- var rest = segs.slice(3);
- if (rest.length) p += rest.join('/') + '/';
- return p;
- }
-
- // List the parties under a project's archive/ (folder names), sorted.
+ // List the registered parties for a project — one ssr/.yaml per
+ // party (the authoritative registry). A party "exists" iff its ssr row
+ // exists, so this is the canonical source for the picker. Returns []
+ // on error.
async function fetchParties(project) {
try {
- var entries = await loader.fetchServerChildren('/' + project + '/archive/');
+ var entries = await loader.fetchServerChildren('/' + project + '/ssr/');
return entries
- .filter(function (e) { return e.isDir; })
- .map(function (e) { return e.name; })
+ .filter(function (e) { return !e.isDir && /\.yaml$/i.test(e.name); })
+ .map(function (e) { return e.name.replace(/\.yaml$/i, ''); })
+ .filter(function (n) { return n !== 'table' && n !== 'form'; })
.sort(function (a, b) { return a.localeCompare(b); });
} catch (_e) { return []; }
}
@@ -13236,8 +13221,8 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
box.innerHTML =
'
New ' + kindWord + ' in ' + escapeHtml(opts.slot) + '/
' +
'
' +
- escapeHtml(opts.slot) + '/ aggregates each party’s work, so it has no folder of its own. ' +
- 'Pick the party this ' + kindWord + ' belongs to — it lands under archive/<party>/' + escapeHtml(opts.slot) + '/.' +
+ escapeHtml(opts.slot) + '/ is partitioned by party. ' +
+ 'Pick the party this ' + kindWord + ' belongs to — it lands under ' + escapeHtml(opts.slot) + '/<party>/.' +
'
' +
'
' +
(partyList || 'No parties yet — create one below.') +
@@ -13288,13 +13273,15 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
var nv = validateName(box.querySelector('#pp-name').value);
if (!nv.ok) { statusError(nv.msg); return; }
close();
- resolve({ party: party, name: nv.name });
+ resolve({ party: party, name: nv.name, isNew: sel.value === '__new__' });
});
});
}
- // createInAggregator routes a New folder/file in a virtual aggregator
- // root to archive/// after prompting for the party.
+ // createInAggregator routes a New folder/file at a party-peer root to
+ // the physical /// after prompting for the
+ // party. A brand-new party is registered first by creating its
+ // ssr/.yaml row (the authoritative registry; party_source: ssr).
async function createInAggregator(agg, kind) {
var up = window.app.modules.upload;
if (!up) return;
@@ -13303,42 +13290,45 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
if (!choice) return;
// Party names are validated to a URL-safe charset, so no encoding
// needed for the party segment; makeDir/makeFile encode the leaf.
- var targetDir = '/' + agg.project + '/archive/' + choice.party + '/' + agg.slot + '/';
+ var targetDir = '/' + agg.project + '/' + agg.slot + '/' + choice.party + '/';
try {
+ if (choice.isNew) {
+ // Register the party: its existence is ssr/.yaml.
+ await up.makeFile('/' + agg.project + '/ssr/', choice.party + '.yaml',
+ 'kind: SSR\n', 'application/yaml; charset=utf-8');
+ }
if (kind === 'folder') {
await up.makeDir(targetDir, choice.name);
- statusInfo('Created ' + choice.party + '/' + agg.slot + '/' + choice.name);
+ statusInfo('Created ' + choice.party + '/' + choice.name + ' in ' + agg.slot + '/');
} else {
var name = /\.(md|markdown)$/i.test(choice.name) ? choice.name : choice.name + '.md';
var template = '# ' + name.replace(/\.(md|markdown)$/i, '') + '\n\n';
await up.makeFile(targetDir, name, template, 'text/markdown; charset=utf-8');
- statusInfo('Created ' + choice.party + '/' + agg.slot + '/' + name);
+ statusInfo('Created ' + choice.party + '/' + name + ' in ' + agg.slot + '/');
}
} catch (e) {
var msg = (e && e.message) || String(e);
if (/\b403\b/.test(msg)) {
- statusError('Not allowed — creating a new party requires the document-controller role.');
+ statusError('Not allowed — registering a new party requires the document-controller role.');
+ } else if (/\b409\b/.test(msg)) {
+ statusError('Unknown party — register it first (document controller).');
} else {
statusError('Create failed: ' + msg);
}
return;
}
- // Refresh the aggregator view — the party now appears if it had no
- // content before.
await reloadDir('/' + agg.project + '/' + agg.slot + '/');
}
async function createInDir(parentDir, kind) {
var up = window.app.modules.upload;
if (!up) return;
- // A project-level folder-nav aggregator (working/staging/reviewing)
- // has no physical home — route through the party picker instead of
- // erroring on an unplaceable mkdir/PUT.
+ // At a party-peer root (incoming/working/staging/reviewing) the
+ // create needs a party — route through the picker. Deeper paths
+ // (a party already chosen, e.g. working//…) are physical and
+ // created directly.
var agg = aggregatorRoot(parentDir);
if (agg) return createInAggregator(agg, kind);
- // A party already chosen inside an aggregator view → canonical path.
- var rewritten = rewriteAggregatorPath(parentDir);
- if (rewritten) parentDir = rewritten;
var promptMsg = kind === 'folder'
? 'New folder name (under ' + parentDir + '):'
: 'New markdown filename (under ' + parentDir + '):';
diff --git a/zddc/internal/apps/embedded/classifier.html b/zddc/internal/apps/embedded/classifier.html
index 071ecc4..e11be3d 100644
--- a/zddc/internal/apps/embedded/classifier.html
+++ b/zddc/internal/apps/embedded/classifier.html
@@ -1793,7 +1793,7 @@ body.is-elevated::after {