diff --git a/zddc/internal/apps/embedded/archive.html b/zddc/internal/apps/embedded/archive.html index e1d17de..e767591 100644 --- a/zddc/internal/apps/embedded/archive.html +++ b/zddc/internal/apps/embedded/archive.html @@ -2717,7 +2717,7 @@ td[data-field="trackingNumber"] {
ZDDC Archive - v0.0.27-beta · 2026-06-11 14:40:24 · bc762a7 + v0.0.27-beta · 2026-06-12 02:15:36 · 93ed0d3
diff --git a/zddc/internal/apps/embedded/browse.html b/zddc/internal/apps/embedded/browse.html index 01b8f3b..180eaa0 100644 --- a/zddc/internal/apps/embedded/browse.html +++ b/zddc/internal/apps/embedded/browse.html @@ -2824,7 +2824,7 @@ li.CodeMirror-hint-active {
ZDDC Browse - v0.0.27-beta · 2026-06-11 14:40:25 · bc762a7 + v0.0.27-beta · 2026-06-12 02:15:36 · 93ed0d3
diff --git a/zddc/internal/apps/embedded/classifier.html b/zddc/internal/apps/embedded/classifier.html index e9d0187..7e1e5be 100644 --- a/zddc/internal/apps/embedded/classifier.html +++ b/zddc/internal/apps/embedded/classifier.html @@ -1741,6 +1741,60 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o cursor: wait; } +/* ── Target tabs: grouped (assign a tracking number) + separate (route) ───── */ +.pane-header--target { flex-wrap: wrap; } +.target-goal { flex: 1 0 100%; margin: 0 0 0.4rem; font-size: 0.78rem; color: var(--text-muted); line-height: 1.4; } +.target-goal strong { color: var(--text); } +.target-goal em { font-style: normal; font-weight: 600; color: var(--text); } +.target-tabs__group { display: flex; gap: 0.25rem; } +.target-tabs__divider { width: 1px; align-self: stretch; margin: 0.2rem 0.6rem 0; background: var(--border); } +/* The "By existing" catalog is now a normal in-flow tab panel. */ +#mdlTree { flex: 1; min-height: 0; } +#mdlTree .seltable { height: 100%; } +.mdl-rev__input { + width: 8rem; padding: 0.15rem 0.35rem; border: 1px solid var(--border); + border-radius: var(--radius); background: var(--bg); color: var(--text); font-size: 0.8rem; +} +.seltable__extra { white-space: normal; } +.mdlfile__name { font-size: 0.78rem; } +#mdlPanel .tfile { gap: 0.3rem; align-items: center; padding: 0.05rem 0; cursor: grab; } +#mdlPanel .tfile--err .mdlfile__name { color: var(--danger); } +#mdlPanel .tfile__remove { opacity: 0.6; } +#mdlPanel .tfile:hover .tfile__remove { opacity: 1; } + +/* ── MDL-from-archive overlay ───────────────────────────────────────────── */ +.mdl-overlay { position: fixed; inset: 0; z-index: 1100; background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center; padding: 2rem 1rem; } +.mdl-overlay__box { background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 10px 40px rgba(0,0,0,0.3); width: 100%; max-width: 1000px; height: 80vh; display: flex; flex-direction: column; } +.mdl-overlay__head { display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem; border-bottom: 1px solid var(--border); } +.mdl-overlay__head h2 { margin: 0; font-size: 1.1rem; } +.mdl-overlay__close { background: none; border: none; font-size: 1.6rem; line-height: 1; color: var(--text-muted); cursor: pointer; padding: 0 0.4rem; } +.mdl-overlay__close:hover { color: var(--text); } +.mdl-overlay__status { padding: 0.4rem 1rem; color: var(--text-muted); font-size: 0.82rem; border-bottom: 1px solid var(--border); } +.mdl-overlay__table { flex: 1; min-height: 0; } +.mdl-overlay__foot { display: flex; justify-content: flex-end; gap: 0.5rem; padding: 0.75rem 1rem; border-top: 1px solid var(--border); } + +/* ── Shared selectable + autofilter table (seltable) ────────────────────── */ +.seltable { display: flex; flex-direction: column; min-height: 0; height: 100%; } +.seltable__bar { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.5rem; border-bottom: 1px solid var(--border); flex: 0 0 auto; } +.seltable__filter { + flex: 1; min-width: 8rem; padding: 0.3rem 0.5rem; + border: 1px solid var(--border); border-radius: var(--radius); + background: var(--bg-secondary, var(--bg)); color: var(--text); font-size: 0.85rem; +} +.seltable__count { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; } +.seltable__scroll { flex: 1; min-height: 0; overflow: auto; } +.seltable__table { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 0.82rem; } +.seltable__table th, .seltable__table td { border-bottom: 1px solid var(--border); padding: 0.25rem 0.5rem; text-align: left; white-space: nowrap; } +.seltable__table thead th { + position: sticky; top: 0; z-index: 2; background: var(--bg-secondary, var(--bg)); + color: var(--text-muted); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; +} +.seltable__row { cursor: pointer; user-select: none; } +.seltable__row:hover { background: var(--bg-hover); } +.seltable__row.is-selected { background: var(--primary-light, rgba(37,99,235,0.12)); } +.seltable__row.is-selected:hover { background: var(--primary-light, rgba(37,99,235,0.18)); } +.seltable__row.drop-hover { outline: 2px solid var(--primary); outline-offset: -2px; } + /* ── Copy destination dialog ────────────────────────────────────────────── */ .copy-choice__backdrop { position: fixed; inset: 0; z-index: 1000; @@ -1762,6 +1816,18 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o background: var(--bg-secondary, var(--bg)); color: var(--text); font-size: 0.9rem; } .copy-choice__btns { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem; } +.copy-choice--wide { max-width: 560px; } + +/* ── Directory picker (lazy multi-select tree inside the copy-choice modal) ─ */ +.dir-picker__tree { + max-height: 50vh; overflow: auto; margin: 0 0 1rem; + border: 1px solid var(--border); border-radius: var(--radius); padding: 0.4rem; +} +.dir-picker__row { display: flex; align-items: center; gap: 0.35rem; font-size: 0.85rem; padding: 0.05rem 0; } +.dir-picker__twisty { width: 1rem; text-align: center; cursor: pointer; color: var(--text-muted); user-select: none; } +.dir-picker__name { cursor: pointer; } +.dir-picker__children { margin-left: 1.1rem; } +.dir-picker__err { color: var(--danger); font-size: 0.78rem; margin-left: 1.1rem; } /* ── By-tracking merged-cell table ──────────────────────────────────────── */ #trackingTree { padding: 0; } /* table reaches the edges; cells carry padding */ @@ -2296,7 +2362,7 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o
ZDDC Classifier - v0.0.27-beta · 2026-06-11 14:40:24 · bc762a7 + v0.0.27-beta · 2026-06-12 02:15:36 · 93ed0d3
@@ -2428,10 +2494,17 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o
-
+
+

Each file needs a tracking number (revision + status + title) and a transmittal folder. Name it — build one under By tracking number or reuse one under By existing — then route it under By transmittal.

- - +
+ + +
+ +
+ +
@@ -2463,6 +2536,17 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o placeholder="Filter the transmittal tree…" aria-label="Filter transmittal tree">
+ +
@@ -3818,6 +3902,9 @@ X.B(E,Y);return E}return J}()) })(UTIF, pako); })(); +/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).jsyaml={})}(this,(function(e){"use strict";function t(e){return null==e}var n={isNothing:t,isObject:function(e){return"object"==typeof e&&null!==e},toArray:function(e){return Array.isArray(e)?e:t(e)?[]:[e]},repeat:function(e,t){var n,i="";for(n=0;nl&&(t=i-l+(o=" ... ").length),n-i>l&&(n=i+l-(a=" ...").length),{str:o+e.slice(t,n).replace(/\t/g,"→")+a,pos:i-t+o.length}}function l(e,t){return n.repeat(" ",t-e.length)+e}var c=function(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var i,r=/\r?\n|\r|\0/g,o=[0],c=[],s=-1;i=r.exec(e.buffer);)c.push(i.index),o.push(i.index+i[0].length),e.position<=i.index&&s<0&&(s=o.length-2);s<0&&(s=o.length-1);var u,p,f="",d=Math.min(e.line+t.linesAfter,c.length).toString().length,h=t.maxLength-(t.indent+d+3);for(u=1;u<=t.linesBefore&&!(s-u<0);u++)p=a(e.buffer,o[s-u],c[s-u],e.position-(o[s]-o[s-u]),h),f=n.repeat(" ",t.indent)+l((e.line-u+1).toString(),d)+" | "+p.str+"\n"+f;for(p=a(e.buffer,o[s],c[s],e.position,h),f+=n.repeat(" ",t.indent)+l((e.line+1).toString(),d)+" | "+p.str+"\n",f+=n.repeat("-",t.indent+d+3+p.pos)+"^\n",u=1;u<=t.linesAfter&&!(s+u>=c.length);u++)p=a(e.buffer,o[s+u],c[s+u],e.position-(o[s]-o[s+u]),h),f+=n.repeat(" ",t.indent)+l((e.line+u+1).toString(),d)+" | "+p.str+"\n";return f.replace(/\n$/,"")},s=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],u=["scalar","sequence","mapping"];var p=function(e,t){if(t=t||{},Object.keys(t).forEach((function(t){if(-1===s.indexOf(t))throw new o('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=function(e){var t={};return null!==e&&Object.keys(e).forEach((function(n){e[n].forEach((function(e){t[String(e)]=n}))})),t}(t.styleAliases||null),-1===u.indexOf(this.kind))throw new o('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')};function f(e,t){var n=[];return e[t].forEach((function(e){var t=n.length;n.forEach((function(n,i){n.tag===e.tag&&n.kind===e.kind&&n.multi===e.multi&&(t=i)})),n[t]=e})),n}function d(e){return this.extend(e)}d.prototype.extend=function(e){var t=[],n=[];if(e instanceof p)n.push(e);else if(Array.isArray(e))n=n.concat(e);else{if(!e||!Array.isArray(e.implicit)&&!Array.isArray(e.explicit))throw new o("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })");e.implicit&&(t=t.concat(e.implicit)),e.explicit&&(n=n.concat(e.explicit))}t.forEach((function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.");if(e.loadKind&&"scalar"!==e.loadKind)throw new o("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.");if(e.multi)throw new o("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.")})),n.forEach((function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.")}));var i=Object.create(d.prototype);return i.implicit=(this.implicit||[]).concat(t),i.explicit=(this.explicit||[]).concat(n),i.compiledImplicit=f(i,"implicit"),i.compiledExplicit=f(i,"explicit"),i.compiledTypeMap=function(){var e,t,n={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}};function i(e){e.multi?(n.multi[e.kind].push(e),n.multi.fallback.push(e)):n[e.kind][e.tag]=n.fallback[e.tag]=e}for(e=0,t=arguments.length;e=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0o"+e.toString(8):"-0o"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}}),x=new RegExp("^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");var I=/^[-+]?[0-9]+e/;var S=new p("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!x.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n;return n="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||n.isNegativeZero(e))},represent:function(e,t){var i;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(n.isNegativeZero(e))return"-0.0";return i=e.toString(10),I.test(i)?i.replace("e",".e"):i},defaultStyle:"lowercase"}),O=b.extend({implicit:[A,v,C,S]}),j=O,T=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),N=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");var F=new p("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:function(e){return null!==e&&(null!==T.exec(e)||null!==N.exec(e))},construct:function(e){var t,n,i,r,o,a,l,c,s=0,u=null;if(null===(t=T.exec(e))&&(t=N.exec(e)),null===t)throw new Error("Date resolve error");if(n=+t[1],i=+t[2]-1,r=+t[3],!t[4])return new Date(Date.UTC(n,i,r));if(o=+t[4],a=+t[5],l=+t[6],t[7]){for(s=t[7].slice(0,3);s.length<3;)s+="0";s=+s}return t[9]&&(u=6e4*(60*+t[10]+ +(t[11]||0)),"-"===t[9]&&(u=-u)),c=new Date(Date.UTC(n,i,r,o,a,l,s)),u&&c.setTime(c.getTime()-u),c},instanceOf:Date,represent:function(e){return e.toISOString()}});var E=new p("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}}),M="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";var L=new p("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,i=0,r=e.length,o=M;for(n=0;n64)){if(t<0)return!1;i+=6}return i%8==0},construct:function(e){var t,n,i=e.replace(/[\r\n=]/g,""),r=i.length,o=M,a=0,l=[];for(t=0;t>16&255),l.push(a>>8&255),l.push(255&a)),a=a<<6|o.indexOf(i.charAt(t));return 0===(n=r%4*6)?(l.push(a>>16&255),l.push(a>>8&255),l.push(255&a)):18===n?(l.push(a>>10&255),l.push(a>>2&255)):12===n&&l.push(a>>4&255),new Uint8Array(l)},predicate:function(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)},represent:function(e){var t,n,i="",r=0,o=e.length,a=M;for(t=0;t>18&63],i+=a[r>>12&63],i+=a[r>>6&63],i+=a[63&r]),r=(r<<8)+e[t];return 0===(n=o%3)?(i+=a[r>>18&63],i+=a[r>>12&63],i+=a[r>>6&63],i+=a[63&r]):2===n?(i+=a[r>>10&63],i+=a[r>>4&63],i+=a[r<<2&63],i+=a[64]):1===n&&(i+=a[r>>2&63],i+=a[r<<4&63],i+=a[64],i+=a[64]),i}}),_=Object.prototype.hasOwnProperty,D=Object.prototype.toString;var U=new p("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,i,r,o,a=[],l=e;for(t=0,n=l.length;t>10),56320+(e-65536&1023))}for(var ie=new Array(256),re=new Array(256),oe=0;oe<256;oe++)ie[oe]=te(oe)?1:0,re[oe]=te(oe);function ae(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||K,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.firstTabInLine=-1,this.documents=[]}function le(e,t){var n={name:e.filename,buffer:e.input.slice(0,-1),position:e.position,line:e.line,column:e.position-e.lineStart};return n.snippet=c(n),new o(t,n)}function ce(e,t){throw le(e,t)}function se(e,t){e.onWarning&&e.onWarning.call(null,le(e,t))}var ue={YAML:function(e,t,n){var i,r,o;null!==e.version&&ce(e,"duplication of %YAML directive"),1!==n.length&&ce(e,"YAML directive accepts exactly one argument"),null===(i=/^([0-9]+)\.([0-9]+)$/.exec(n[0]))&&ce(e,"ill-formed argument of the YAML directive"),r=parseInt(i[1],10),o=parseInt(i[2],10),1!==r&&ce(e,"unacceptable YAML version of the document"),e.version=n[0],e.checkLineBreaks=o<2,1!==o&&2!==o&&se(e,"unsupported YAML version of the document")},TAG:function(e,t,n){var i,r;2!==n.length&&ce(e,"TAG directive accepts exactly two arguments"),i=n[0],r=n[1],G.test(i)||ce(e,"ill-formed tag handle (first argument) of the TAG directive"),P.call(e.tagMap,i)&&ce(e,'there is a previously declared suffix for "'+i+'" tag handle'),V.test(r)||ce(e,"ill-formed tag prefix (second argument) of the TAG directive");try{r=decodeURIComponent(r)}catch(t){ce(e,"tag prefix is malformed: "+r)}e.tagMap[i]=r}};function pe(e,t,n,i){var r,o,a,l;if(t1&&(e.result+=n.repeat("\n",t-1))}function be(e,t){var n,i,r=e.tag,o=e.anchor,a=[],l=!1;if(-1!==e.firstTabInLine)return!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=a),i=e.input.charCodeAt(e.position);0!==i&&(-1!==e.firstTabInLine&&(e.position=e.firstTabInLine,ce(e,"tab characters must not be used in indentation")),45===i)&&z(e.input.charCodeAt(e.position+1));)if(l=!0,e.position++,ge(e,!0,-1)&&e.lineIndent<=t)a.push(null),i=e.input.charCodeAt(e.position);else if(n=e.line,we(e,t,3,!1,!0),a.push(e.result),ge(e,!0,-1),i=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==i)ce(e,"bad indentation of a sequence entry");else if(e.lineIndentt?g=1:e.lineIndent===t?g=0:e.lineIndentt?g=1:e.lineIndent===t?g=0:e.lineIndentt)&&(y&&(a=e.line,l=e.lineStart,c=e.position),we(e,t,4,!0,r)&&(y?g=e.result:m=e.result),y||(de(e,f,d,h,g,m,a,l,c),h=g=m=null),ge(e,!0,-1),s=e.input.charCodeAt(e.position)),(e.line===o||e.lineIndent>t)&&0!==s)ce(e,"bad indentation of a mapping entry");else if(e.lineIndent=0))break;0===o?ce(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):u?ce(e,"repeat of an indentation width identifier"):(p=t+o-1,u=!0)}if(Q(a)){do{a=e.input.charCodeAt(++e.position)}while(Q(a));if(35===a)do{a=e.input.charCodeAt(++e.position)}while(!J(a)&&0!==a)}for(;0!==a;){for(he(e),e.lineIndent=0,a=e.input.charCodeAt(e.position);(!u||e.lineIndentp&&(p=e.lineIndent),J(a))f++;else{if(e.lineIndent0){for(r=a,o=0;r>0;r--)(a=ee(l=e.input.charCodeAt(++e.position)))>=0?o=(o<<4)+a:ce(e,"expected hexadecimal character");e.result+=ne(o),e.position++}else ce(e,"unknown escape sequence");n=i=e.position}else J(l)?(pe(e,n,i,!0),ye(e,ge(e,!1,t)),n=i=e.position):e.position===e.lineStart&&me(e)?ce(e,"unexpected end of the document within a double quoted scalar"):(e.position++,i=e.position)}ce(e,"unexpected end of the stream within a double quoted scalar")}(e,d)?y=!0:!function(e){var t,n,i;if(42!==(i=e.input.charCodeAt(e.position)))return!1;for(i=e.input.charCodeAt(++e.position),t=e.position;0!==i&&!z(i)&&!X(i);)i=e.input.charCodeAt(++e.position);return e.position===t&&ce(e,"name of an alias node must contain at least one character"),n=e.input.slice(t,e.position),P.call(e.anchorMap,n)||ce(e,'unidentified alias "'+n+'"'),e.result=e.anchorMap[n],ge(e,!0,-1),!0}(e)?function(e,t,n){var i,r,o,a,l,c,s,u,p=e.kind,f=e.result;if(z(u=e.input.charCodeAt(e.position))||X(u)||35===u||38===u||42===u||33===u||124===u||62===u||39===u||34===u||37===u||64===u||96===u)return!1;if((63===u||45===u)&&(z(i=e.input.charCodeAt(e.position+1))||n&&X(i)))return!1;for(e.kind="scalar",e.result="",r=o=e.position,a=!1;0!==u;){if(58===u){if(z(i=e.input.charCodeAt(e.position+1))||n&&X(i))break}else if(35===u){if(z(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&me(e)||n&&X(u))break;if(J(u)){if(l=e.line,c=e.lineStart,s=e.lineIndent,ge(e,!1,-1),e.lineIndent>=t){a=!0,u=e.input.charCodeAt(e.position);continue}e.position=o,e.line=l,e.lineStart=c,e.lineIndent=s;break}}a&&(pe(e,r,o,!1),ye(e,e.line-l),r=o=e.position,a=!1),Q(u)||(o=e.position+1),u=e.input.charCodeAt(++e.position)}return pe(e,r,o,!1),!!e.result||(e.kind=p,e.result=f,!1)}(e,d,1===i)&&(y=!0,null===e.tag&&(e.tag="?")):(y=!0,null===e.tag&&null===e.anchor||ce(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===g&&(y=c&&be(e,h))),null===e.tag)null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);else if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&ce(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),s=0,u=e.implicitTypes.length;s"),null!==e.result&&f.kind!==e.kind&&ce(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+f.kind+'", not "'+e.kind+'"'),f.resolve(e.result,e.tag)?(e.result=f.construct(e.result,e.tag),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):ce(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")}return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||y}function ke(e){var t,n,i,r,o=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap=Object.create(null),e.anchorMap=Object.create(null);0!==(r=e.input.charCodeAt(e.position))&&(ge(e,!0,-1),r=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==r));){for(a=!0,r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!z(r);)r=e.input.charCodeAt(++e.position);for(i=[],(n=e.input.slice(t,e.position)).length<1&&ce(e,"directive name must not be less than one character in length");0!==r;){for(;Q(r);)r=e.input.charCodeAt(++e.position);if(35===r){do{r=e.input.charCodeAt(++e.position)}while(0!==r&&!J(r));break}if(J(r))break;for(t=e.position;0!==r&&!z(r);)r=e.input.charCodeAt(++e.position);i.push(e.input.slice(t,e.position))}0!==r&&he(e),P.call(ue,n)?ue[n](e,n,i):se(e,'unknown document directive "'+n+'"')}ge(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,ge(e,!0,-1)):a&&ce(e,"directives end mark is expected"),we(e,e.lineIndent-1,4,!1,!0),ge(e,!0,-1),e.checkLineBreaks&&H.test(e.input.slice(o,e.position))&&se(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&me(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,ge(e,!0,-1)):e.position=55296&&i<=56319&&t+1=56320&&n<=57343?1024*(i-55296)+n-56320+65536:i}function Re(e){return/^\n* /.test(e)}function Be(e,t,n,i,r,o,a,l){var c,s,u=0,p=null,f=!1,d=!1,h=-1!==i,g=-1,m=De(s=Ye(e,0))&&s!==Oe&&!_e(s)&&45!==s&&63!==s&&58!==s&&44!==s&&91!==s&&93!==s&&123!==s&&125!==s&&35!==s&&38!==s&&42!==s&&33!==s&&124!==s&&61!==s&&62!==s&&39!==s&&34!==s&&37!==s&&64!==s&&96!==s&&function(e){return!_e(e)&&58!==e}(Ye(e,e.length-1));if(t||a)for(c=0;c=65536?c+=2:c++){if(!De(u=Ye(e,c)))return 5;m=m&&qe(u,p,l),p=u}else{for(c=0;c=65536?c+=2:c++){if(10===(u=Ye(e,c)))f=!0,h&&(d=d||c-g-1>i&&" "!==e[g+1],g=c);else if(!De(u))return 5;m=m&&qe(u,p,l),p=u}d=d||h&&c-g-1>i&&" "!==e[g+1]}return f||d?n>9&&Re(e)?5:a?2===o?5:2:d?4:3:!m||a||r(e)?2===o?5:2:1}function Ke(e,t,n,i,r){e.dump=function(){if(0===t.length)return 2===e.quotingType?'""':"''";if(!e.noCompatMode&&(-1!==Te.indexOf(t)||Ne.test(t)))return 2===e.quotingType?'"'+t+'"':"'"+t+"'";var a=e.indent*Math.max(1,n),l=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-a),c=i||e.flowLevel>-1&&n>=e.flowLevel;switch(Be(t,c,e.indent,l,(function(t){return function(e,t){var n,i;for(n=0,i=e.implicitTypes.length;n"+Pe(t,e.indent)+We(Me(function(e,t){var n,i,r=/(\n+)([^\n]*)/g,o=(l=e.indexOf("\n"),l=-1!==l?l:e.length,r.lastIndex=l,He(e.slice(0,l),t)),a="\n"===e[0]||" "===e[0];var l;for(;i=r.exec(e);){var c=i[1],s=i[2];n=" "===s[0],o+=c+(a||n||""===s?"":"\n")+He(s,t),a=n}return o}(t,l),a));case 5:return'"'+function(e){for(var t,n="",i=0,r=0;r=65536?r+=2:r++)i=Ye(e,r),!(t=je[i])&&De(i)?(n+=e[r],i>=65536&&(n+=e[r+1])):n+=t||Fe(i);return n}(t)+'"';default:throw new o("impossible error: invalid scalar style")}}()}function Pe(e,t){var n=Re(e)?String(t):"",i="\n"===e[e.length-1];return n+(i&&("\n"===e[e.length-2]||"\n"===e)?"+":i?"":"-")+"\n"}function We(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function He(e,t){if(""===e||" "===e[0])return e;for(var n,i,r=/ [^ ]/g,o=0,a=0,l=0,c="";n=r.exec(e);)(l=n.index)-o>t&&(i=a>o?a:l,c+="\n"+e.slice(o,i),o=i+1),a=l;return c+="\n",e.length-o>t&&a>o?c+=e.slice(o,a)+"\n"+e.slice(a+1):c+=e.slice(o),c.slice(1)}function $e(e,t,n,i){var r,o,a,l="",c=e.tag;for(r=0,o=n.length;r tag resolver accepts not "'+s+'" style');i=c.represent[s](t,s)}e.dump=i}return!0}return!1}function Ve(e,t,n,i,r,a,l){e.tag=null,e.dump=n,Ge(e,n,!1)||Ge(e,n,!0);var c,s=Ie.call(e.dump),u=i;i&&(i=e.flowLevel<0||e.flowLevel>t);var p,f,d="[object Object]"===s||"[object Array]"===s;if(d&&(f=-1!==(p=e.duplicates.indexOf(n))),(null!==e.tag&&"?"!==e.tag||f||2!==e.indent&&t>0)&&(r=!1),f&&e.usedDuplicates[p])e.dump="*ref_"+p;else{if(d&&f&&!e.usedDuplicates[p]&&(e.usedDuplicates[p]=!0),"[object Object]"===s)i&&0!==Object.keys(e.dump).length?(!function(e,t,n,i){var r,a,l,c,s,u,p="",f=e.tag,d=Object.keys(n);if(!0===e.sortKeys)d.sort();else if("function"==typeof e.sortKeys)d.sort(e.sortKeys);else if(e.sortKeys)throw new o("sortKeys must be a boolean or a function");for(r=0,a=d.length;r1024)&&(e.dump&&10===e.dump.charCodeAt(0)?u+="?":u+="? "),u+=e.dump,s&&(u+=Le(e,t)),Ve(e,t+1,c,!0,s)&&(e.dump&&10===e.dump.charCodeAt(0)?u+=":":u+=": ",p+=u+=e.dump));e.tag=f,e.dump=p||"{}"}(e,t,e.dump,r),f&&(e.dump="&ref_"+p+e.dump)):(!function(e,t,n){var i,r,o,a,l,c="",s=e.tag,u=Object.keys(n);for(i=0,r=u.length;i1024&&(l+="? "),l+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),Ve(e,t,a,!1,!1)&&(c+=l+=e.dump));e.tag=s,e.dump="{"+c+"}"}(e,t,e.dump),f&&(e.dump="&ref_"+p+" "+e.dump));else if("[object Array]"===s)i&&0!==e.dump.length?(e.noArrayIndent&&!l&&t>0?$e(e,t-1,e.dump,r):$e(e,t,e.dump,r),f&&(e.dump="&ref_"+p+e.dump)):(!function(e,t,n){var i,r,o,a="",l=e.tag;for(i=0,r=n.length;i",e.dump=c+" "+e.dump)}return!0}function Ze(e,t){var n,i,r=[],o=[];for(Je(e,r,o),n=0,i=o.length;n { node, kind:'tracking'|'party'|'slot'|'transmittal', parent } @@ -7385,7 +7475,7 @@ X.B(E,Y);return E}return J}()) function assignmentFor(key) { var a = state.assignments[key]; if (!a) { - a = { trackingNodeId: null, transmittalNodeId: null, excluded: false, titleOverride: null }; + a = { trackingNodeId: null, transmittalNodeId: null, mdlNodeId: null, excluded: false, titleOverride: null, titleFromDeliverable: true }; state.assignments[key] = a; } return a; @@ -7394,7 +7484,7 @@ X.B(E,Y);return E}return J}()) function getAssignment(key) { return state.assignments[key] || null; } function cleanAssignment(key) { var a = state.assignments[key]; - if (a && !a.trackingNodeId && !a.transmittalNodeId && !a.excluded && !a.titleOverride) { + if (a && !a.trackingNodeId && !a.transmittalNodeId && !a.mdlNodeId && !a.excluded && !a.titleOverride) { delete state.assignments[key]; } } @@ -7402,10 +7492,14 @@ X.B(E,Y);return E}return J}()) // Place keys onto a node along one axis ('tracking' | 'transmittal'). // nodeId null clears that axis. function place(keys, nodeId, axis) { - var field = axis === 'transmittal' ? 'transmittalNodeId' : 'trackingNodeId'; + var field = axis === 'transmittal' ? 'transmittalNodeId' : axis === 'mdl' ? 'mdlNodeId' : 'trackingNodeId'; keys.forEach(function (k) { var a = assignmentFor(k); a[field] = nodeId || null; + // Tracking and MDL are alternative NAME sources — placing on one + // clears the other so the file has a single name origin. + if (axis === 'mdl' && nodeId) a.trackingNodeId = null; + else if (axis === 'tracking' && nodeId) a.mdlNodeId = null; a.excluded = false; // placing un-excludes cleanAssignment(k); }); @@ -7416,7 +7510,7 @@ X.B(E,Y);return E}return J}()) keys.forEach(function (k) { var a = assignmentFor(k); a.excluded = !!excluded; - if (excluded) { a.trackingNodeId = null; a.transmittalNodeId = null; } + if (excluded) { a.trackingNodeId = null; a.transmittalNodeId = null; a.mdlNodeId = null; } cleanAssignment(k); }); clearHashConflicts(); @@ -7456,6 +7550,9 @@ X.B(E,Y);return E}return J}()) }); }); }); + (state.mdlList || []).forEach(function (row) { + nodeIndex[row.id] = { node: row, kind: 'mdl', parent: null }; + }); } function getNode(id) { return nodeIndex[id] ? nodeIndex[id].node : null; } function infoFor(id) { return nodeIndex[id] || null; } @@ -7588,8 +7685,26 @@ X.B(E,Y);return E}return J}()) }; if (out.excluded) return out; - // Axis 1 — tracking. - if (a.trackingNodeId) { + // Axis 1 — NAME. An MDL deliverable (alternative to the tracking tree) + // supplies the tracking number + title; its revision comes from the + // classifier-local revision cell. Otherwise the tracking tree. + if (a.mdlNodeId) { + var mi = infoFor(a.mdlNodeId); + if (mi && mi.kind === 'mdl') { + var row = mi.node; + out.tracking = row.trackingNumber || ''; + var ml = parseLeafLabel(row.revisionCell || ''); + out.revision = ml.revision; out.status = ml.status; + out.trackingLeaf = true; + if (!a.titleOverride && a.titleFromDeliverable !== false && row.title) out.title = row.title; + if (!out.tracking) out.errors.push('deliverable has no tracking number'); + if (!out.revision) out.errors.push('set a revision for this deliverable (e.g. "A (IFR)")'); + else if (out.status && !zddc.isValidStatus(out.status)) out.errors.push('unknown status "' + out.status + '"'); + else if (!out.status) out.errors.push('revision needs a "(STATUS)" — e.g. "A (IFR)"'); + } else { + out.errors.push('deliverable no longer loaded'); + } + } else if (a.trackingNodeId) { var ti = infoFor(a.trackingNodeId); if (ti && ti.kind === 'tracking') { var chain = trackingChain(ti); // [root … node] @@ -7679,6 +7794,7 @@ X.B(E,Y);return E}return J}()) transmittalTree: state.transmittalTree, outputName: state.outputName, config: state.config, + mdlList: state.mdlList, }; } function load(obj) { @@ -7688,6 +7804,7 @@ X.B(E,Y);return E}return J}()) state.transmittalTree = obj.transmittalTree || []; state.outputName = obj.outputName || null; state.config = normalizeConfig(obj.config); + state.mdlList = Array.isArray(obj.mdlList) ? obj.mdlList : []; rebuildIndex(); notify(); } @@ -7717,6 +7834,43 @@ X.B(E,Y);return E}return J}()) function getTrackingFields() { return state.config.trackingFields; } function setConfig(c) { state.config = normalizeConfig(c); notify(); } + // ── MDL deliverables (the "By MDL" drop-target axis) ───────────────────── + function setMdlList(rows) { + state.mdlList = (rows || []).map(function (r) { + return { + id: r.id || uid(), party: r.party || '', + trackingNumber: r.trackingNumber || '', title: r.title || '', + inMdl: !!r.inMdl, + archiveRevisions: Array.isArray(r.archiveRevisions) ? r.archiveRevisions : [], + revisionCell: r.revisionCell || '', + }; + }); + // Drop placements pointing at deliverables no longer loaded. + var valid = Object.create(null); + state.mdlList.forEach(function (r) { valid[r.id] = true; }); + Object.keys(state.assignments).forEach(function (k) { + var a = state.assignments[k]; + if (a.mdlNodeId && !valid[a.mdlNodeId]) { a.mdlNodeId = null; cleanAssignment(k); } + }); + rebuildIndex(); + notify(); + } + function getMdlList() { return state.mdlList; } + function getMdlRow(id) { var i = infoFor(id); return (i && i.kind === 'mdl') ? i.node : null; } + function setRevisionCell(rowId, value) { setRevisionCells([rowId], value); } + function setRevisionCells(rowIds, value) { + var set = Object.create(null); (rowIds || []).forEach(function (i) { set[i] = true; }); + var changed = false; + state.mdlList.forEach(function (r) { if (set[r.id]) { r.revisionCell = (value == null ? '' : String(value)); changed = true; } }); + if (changed) notify(); + } + function setTitleFromDeliverable(key, fromDeliverable) { + var a = assignmentFor(key); + a.titleFromDeliverable = !!fromDeliverable; + cleanAssignment(key); + notify(); + } + // ── add-folder pattern expansion ───────────────────────────────────────── // Brace expansion for the add-folder box. Supports (non-nested) groups: // {a,b,c} → alternation: a | b | c @@ -7891,6 +8045,9 @@ X.B(E,Y);return E}return J}()) transmittalRecord: transmittalRecord, findOrAddParty: findOrAddParty, findOrAddTransmittalBin: findOrAddTransmittalBin, getConfig: getConfig, setConfig: setConfig, getTrackingFields: getTrackingFields, + setMdlList: setMdlList, getMdlList: getMdlList, getMdlRow: getMdlRow, + setRevisionCell: setRevisionCell, setRevisionCells: setRevisionCells, + setTitleFromDeliverable: setTitleFromDeliverable, getNode: getNode, getTrackingTree: function () { return state.trackingTree; }, getTransmittalTree: function () { return state.transmittalTree; }, // derive + reverse @@ -8317,6 +8474,185 @@ X.B(E,Y);return E}return J}()) }; })(); +/** + * ZDDC — shared selectable + autofilter table (used by the classifier catalog + * and the tables tool's "Add from archive"). + * + * A flat table with PER-COLUMN autofilters (one input per column, AND-combined, + * each an AND of space-separated terms) plus an optional programmatic global + * filter, and powerful selection for building complex sets quickly: + * click replace selection + set anchor + * ctrl/cmd-click toggle one row + * shift-click range from the anchor (replaces the selection) + * ctrl-shift-click ADD the anchor→row range to the existing selection + * ctrl/cmd-Enter fire onActivate(selectedIds) — a bulk action + * Esc clear + * Ranges run over the CURRENTLY FILTERED order. Selection is keyed by a stable + * rowId so it survives filtering and re-render. + */ +(function () { + 'use strict'; + if (!window.app) window.app = {}; + if (!window.app.modules) window.app.modules = {}; + + function terms(q) { return String(q == null ? '' : q).trim().toLowerCase().split(/\s+/).filter(Boolean); } + function hit(text, ts) { + var t = String(text == null ? '' : text).toLowerCase(); + for (var i = 0; i < ts.length; i++) { if (t.indexOf(ts[i]) === -1) return false; } + return true; + } + function elt(tag, cls, text) { + var e = document.createElement(tag); + if (cls) e.className = cls; + if (text != null) e.textContent = text; + return e; + } + + function create(opts) { + var container = opts.container; + var columns = opts.columns || []; + var rowId = opts.rowId || function (r) { return r.id; }; + var getRows = (typeof opts.rows === 'function') ? opts.rows : function () { return opts.rows || []; }; + var selected = Object.create(null); // id -> true + var anchorId = null; + var globalTerms = []; // programmatic global filter (tests/reveal) + var colFilters = Object.create(null); // colKey -> terms[] (the per-column autofilters) + + function rows() { return getRows() || []; } + function colByKey(k) { for (var i = 0; i < columns.length; i++) { if (columns[i].key === k) return columns[i]; } return null; } + function colVal(col, row) { return col.get ? col.get(row) : (row[col.key] == null ? '' : row[col.key]); } + function rowBlob(row) { var s = ''; for (var i = 0; i < columns.length; i++) { s += colVal(columns[i], row) + ' '; } return s; } + function rowMatches(row) { + if (globalTerms.length && !hit(rowBlob(row), globalTerms)) return false; + for (var k in colFilters) { + var col = colByKey(k); + if (col && !hit(colVal(col, row), colFilters[k])) return false; + } + return true; + } + function filtered() { return rows().filter(rowMatches); } + + function getSelection() { return Object.keys(selected); } + function getFilteredRows() { return filtered(); } + function fireSel() { if (opts.onSelectionChange) opts.onSelectionChange(getSelection()); } + function setFilter(q) { globalTerms = terms(q); renderBody(); } + function setColFilter(colKey, q) { var t = terms(q); if (t.length) colFilters[colKey] = t; else delete colFilters[colKey]; renderBody(); } + function selectAllFiltered() { filtered().forEach(function (r) { selected[rowId(r)] = true; }); anchorId = null; renderBody(); fireSel(); } + function clearSel() { selected = Object.create(null); anchorId = null; renderBody(); fireSel(); } + + function onRowClick(e, row, fr) { + var ids = fr.map(rowId), id = rowId(row), idx = ids.indexOf(id), aIdx; + if (e.shiftKey && anchorId != null && (aIdx = ids.indexOf(anchorId)) >= 0) { + if (!(e.ctrlKey || e.metaKey)) selected = Object.create(null); // shift replaces; ctrl-shift adds + var lo = Math.min(aIdx, idx), hi = Math.max(aIdx, idx); + for (var i = lo; i <= hi; i++) selected[ids[i]] = true; + } else if (e.ctrlKey || e.metaKey) { + if (selected[id]) delete selected[id]; else selected[id] = true; + anchorId = id; + } else { + selected = Object.create(null); selected[id] = true; anchorId = id; + } + renderBody(); fireSel(); + } + + var bodyEl = null, countEl = null; + function render() { + container.textContent = ''; + container.classList.add('seltable'); + var bar = elt('div', 'seltable__bar'); + var allBtn = elt('button', 'btn btn-sm btn-secondary', 'Select filtered'); + allBtn.addEventListener('click', selectAllFiltered); + var clrBtn = elt('button', 'btn btn-sm btn-secondary', 'Clear'); + clrBtn.addEventListener('click', clearSel); + countEl = elt('span', 'seltable__count'); + bar.appendChild(allBtn); bar.appendChild(clrBtn); bar.appendChild(countEl); + container.appendChild(bar); + + var scroll = elt('div', 'seltable__scroll'); + var table = elt('table', 'seltable__table'); + var thead = elt('thead'), htr = elt('tr'); + columns.forEach(function (c) { htr.appendChild(elt('th', c.cls || null, c.title || c.key)); }); + if (opts.rowExtra) htr.appendChild(elt('th', 'seltable__extrah', opts.extraTitle || '')); + thead.appendChild(htr); + // Per-column autofilter row. + var ftr = elt('tr', 'seltable__filters'); + columns.forEach(function (c) { + var th = elt('th'); + if (c.filterable !== false) { + var inp = elt('input', 'seltable__colfilter'); inp.type = 'search'; inp.placeholder = 'filter…'; inp.spellcheck = false; + inp.setAttribute('data-no-select', ''); + inp.addEventListener('input', function () { setColFilter(c.key, this.value); }); + th.appendChild(inp); + } + ftr.appendChild(th); + }); + if (opts.rowExtra) ftr.appendChild(elt('th')); + thead.appendChild(ftr); + table.appendChild(thead); + bodyEl = elt('tbody'); table.appendChild(bodyEl); + scroll.appendChild(table); container.appendChild(scroll); + + container.tabIndex = 0; + container.addEventListener('keydown', function (e) { + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); if (opts.onActivate) opts.onActivate(getSelection()); } + else if (e.key === 'Escape') { clearSel(); } + }); + renderBody(); + } + function renderBody() { + if (!bodyEl) return; + var fr = filtered(); + bodyEl.textContent = ''; + fr.forEach(function (row) { + var id = rowId(row); + var tr = elt('tr', 'seltable__row' + (selected[id] ? ' is-selected' : '')); + tr.dataset.id = id; + tr.addEventListener('click', function (e) { + if (e.target.closest('input,button,select,a,[data-no-select]')) return; + onRowClick(e, row, fr); + }); + if (opts.onRowDrop) { + tr.addEventListener('dragover', function (e) { + if (window.app.modules.dnd && window.app.modules.dnd.active()) { e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; tr.classList.add('drop-hover'); } + }); + tr.addEventListener('dragleave', function () { tr.classList.remove('drop-hover'); }); + tr.addEventListener('drop', function (e) { + tr.classList.remove('drop-hover'); + e.preventDefault(); + var keys = window.app.modules.dnd ? window.app.modules.dnd.getDrag() : []; + if (window.app.modules.dnd) window.app.modules.dnd.clearDrag(); + if (keys.length) opts.onRowDrop(id, keys); + }); + } + columns.forEach(function (c) { + var td = elt('td', c.cls || null); + if (c.render) c.render(row, td); else td.textContent = colVal(c, row); + tr.appendChild(td); + }); + if (opts.rowExtra) { var ex = elt('td', 'seltable__extra'); opts.rowExtra(row, ex); tr.appendChild(ex); } + bodyEl.appendChild(tr); + }); + if (countEl) { + var nSel = getSelection().length; + countEl.textContent = fr.length + ' shown' + (nSel ? ' · ' + nSel + ' selected' : ''); + } + } + + return { + render: render, renderBody: renderBody, + getSelection: getSelection, getFilteredRows: getFilteredRows, + setFilter: setFilter, setColFilter: setColFilter, selectAllFiltered: selectAllFiltered, clear: clearSel, + clickRow: function (id, mods) { + var fr = filtered(); + var row = fr.filter(function (r) { return String(rowId(r)) === String(id); })[0]; + if (row) onRowClick(Object.assign({ shiftKey: false, ctrlKey: false, metaKey: false }, mods || {}), row, fr); + }, + }; + } + + window.app.modules.seltable = { create: create }; +})(); + /** * ZDDC Validation Module * Validates file names against ZDDC conventions using the shared zddc library. @@ -9434,18 +9770,19 @@ X.B(E,Y);return E}return J}()) var tt = window.app.modules.targetTree; return (tt && tt.activeAxis) ? tt.activeAxis() : 'tracking'; } - // Bucket a file relative to the active axis: - // 'excluded' | 'assigned' (on this axis) | 'partial' (assigned on the OTHER - // axis only — the to-do for this tab) | 'unassigned' (neither axis). + function axisField(ax) { return ax === 'transmittal' ? 'transmittalNodeId' : ax === 'mdl' ? 'mdlNodeId' : 'trackingNodeId'; } + // Bucket a file relative to the active axis (tracking | transmittal | mdl): + // 'excluded' | 'assigned' (on this axis) | 'partial' (assigned on a DIFFERENT + // axis only — the to-do for this tab) | 'unassigned' (no axis). function fileCategory(file) { var c = window.app.modules.classify; var a = c.getAssignment(c.srcKeyForFile(file)); if (a && a.excluded) return 'excluded'; - var onTransmittal = activeAxis() === 'transmittal'; - var here = a && (onTransmittal ? a.transmittalNodeId : a.trackingNodeId); - if (here) return 'assigned'; - var other = a && (onTransmittal ? a.trackingNodeId : a.transmittalNodeId); - return other ? 'partial' : 'unassigned'; + var ax = activeAxis(); + if (a && a[axisField(ax)]) return 'assigned'; + var others = ['tracking', 'transmittal', 'mdl'].filter(function (x) { return x !== ax; }); + var any = a && others.some(function (x) { return a[axisField(x)]; }); + return any ? 'partial' : 'unassigned'; } function classifyAllows(file) { return !classifyOn() || !!showFilters[fileCategory(file)]; } @@ -10297,6 +10634,142 @@ X.B(E,Y);return E}return J}()) }; })(); +/** + * ZDDC Classifier — lazy, multi-select directory picker (modal). + * + * Given one or more root directory handles, render an expandable checkbox tree + * and let the user TICK the directories whose files they want. Ticking a + * directory includes its whole subtree; a descendant under a ticked ancestor + * shows as checked+disabled (covered). Confirm resolves with the TOPMOST ticked + * handles only (a ticked child under a ticked parent is dropped — the parent's + * recursive walk covers it). Cancel/Esc/backdrop → []. + * + * Handle-agnostic: a "handle" is anything exposing async `values()` (yielding + * child handles {name, kind}) and `getDirectoryHandle(name)` — satisfied by both + * zddc-source.js HttpDirectoryHandle and native FileSystemDirectoryHandle. + * + * window.app.modules.dirPicker.pick(roots) → Promise + * roots: [ { label, handle } ] + */ +(function () { + 'use strict'; + if (!window.app) window.app = {}; + if (!window.app.modules) window.app.modules = {}; + + function elt(tag, cls, text) { + var e = document.createElement(tag); + if (cls) e.className = cls; + if (text != null) e.textContent = text; + return e; + } + // Same skip set as the archive walk: dotfiles, system (_), and risk folders. + function hiddenName(nm) { return nm.charAt(0) === '.' || nm.charAt(0) === '_' || nm === 'rsk'; } + + function ancestorChecked(node) { + for (var p = node.parent; p; p = p.parent) { if (p.checked) return true; } + return false; + } + // Topmost ticked handles: a node whose own `checked` is set and which has no + // checked ancestor. Pure over { checked, handle, children } — also the test seam. + function collect(nodes, underChecked, out) { + (nodes || []).forEach(function (n) { + if (n.checked && !underChecked) out.push(n.handle); + collect(n.children, underChecked || !!n.checked, out); + }); + return out; + } + + function pick(roots) { + return new Promise(function (resolve) { + var done = false, rootNodes = []; + function finish(v) { if (done) return; done = true; document.removeEventListener('keydown', onKey); back.remove(); resolve(v); } + function onKey(e) { if (e.key === 'Escape') finish([]); } + + var back = elt('div', 'copy-choice__backdrop'); + var box = elt('div', 'copy-choice copy-choice--wide'); + var h = elt('h3', null, 'Choose directories to scan'); + var p = elt('p', null, 'Tick the directories whose files you want in the catalog — subfolders are included. Expand with ▸.'); + var treeWrap = elt('div', 'dir-picker__tree'); + var btns = elt('div', 'copy-choice__btns'); + var go = elt('button', 'btn btn-primary', 'Scan'); go.disabled = true; + go.addEventListener('click', function () { finish(collect(rootNodes, false, [])); }); + var cancel = elt('button', 'btn btn-secondary', 'Cancel'); + cancel.addEventListener('click', function () { finish([]); }); + btns.appendChild(go); btns.appendChild(cancel); + box.appendChild(h); box.appendChild(p); box.appendChild(treeWrap); box.appendChild(btns); + back.appendChild(box); + back.addEventListener('click', function (e) { if (e.target === back) finish([]); }); + document.addEventListener('keydown', onKey); + document.body.appendChild(back); + + function refreshGo() { go.disabled = collect(rootNodes, false, []).length === 0; } + + // Recompute the displayed checkbox state of a subtree: a node under a + // checked ancestor is forced checked + disabled (inherited coverage). + function recompute(node, inherited) { + node.checkbox.disabled = inherited; + node.checkbox.checked = inherited || node.checked; + var below = inherited || node.checked; + node.children.forEach(function (c) { recompute(c, below); }); + } + + function makeNode(handle, label, parent, container) { + var node = { handle: handle, name: label, parent: parent, checked: false, expanded: false, loaded: false, children: [], childrenWrap: null, checkbox: null }; + var rowEl = elt('div', 'dir-picker__row'); + var twisty = elt('span', 'dir-picker__twisty', '▸'); + var cb = elt('input'); cb.type = 'checkbox'; + var nameEl = elt('span', 'dir-picker__name', label); + twisty.addEventListener('click', function () { toggle(node, twisty); }); + nameEl.addEventListener('click', function () { toggle(node, twisty); }); + cb.addEventListener('change', function () { + if (cb.disabled) return; + node.checked = cb.checked; + recompute(node, ancestorChecked(node)); + refreshGo(); + }); + rowEl.appendChild(twisty); rowEl.appendChild(cb); rowEl.appendChild(nameEl); + var kids = elt('div', 'dir-picker__children'); kids.hidden = true; + node.checkbox = cb; node.childrenWrap = kids; + container.appendChild(rowEl); container.appendChild(kids); + return node; + } + + async function toggle(node, twisty) { + node.expanded = !node.expanded; + node.childrenWrap.hidden = !node.expanded; + twisty.textContent = node.expanded ? '▾' : '▸'; + if (node.expanded && !node.loaded) { + node.loaded = true; + twisty.textContent = '…'; + try { + for await (var e of node.handle.values()) { + if (e.kind !== 'directory') continue; + var nm = String(e.name).replace(/\/$/, ''); + if (hiddenName(nm)) continue; + var childHandle = e.getDirectoryHandle ? e : await node.handle.getDirectoryHandle(nm); + var child = makeNode(childHandle, nm, node, node.childrenWrap); + node.children.push(child); + } + } catch (err) { + node.childrenWrap.appendChild(elt('div', 'dir-picker__err', 'Could not read — ' + (err.message || err))); + } + twisty.textContent = node.children.length ? (node.expanded ? '▾' : '▸') : '·'; + // A freshly-loaded subtree inherits an already-checked ancestor. + recompute(node, ancestorChecked(node)); + } + } + + (roots || []).forEach(function (r) { rootNodes.push(makeNode(r.handle, r.label, null, treeWrap)); }); + if (!rootNodes.length) finish([]); + }); + } + + window.app.modules.dirPicker = { + pick: pick, + _collect: function (nodes) { return collect(nodes, false, []); }, // test seam + }; +})(); + /** * ZDDC Classifier — target-tree pane (Classify & Copy mode). * @@ -10318,25 +10791,33 @@ X.B(E,Y);return E}return J}()) var collapsed = {}; // nodeId -> true when collapsed (default expanded) var openForm = null; // { partyId, slot } when a bin form is open var initialized = false; - var currentTab = 'tracking'; // 'tracking' | 'transmittal' — the active axis + var currentTab = 'tracking'; // 'tracking' | 'existing' | 'transmittal' — active tab + var mdlTable = null; // the seltable controller for the catalog + var mdlPlaced = {}; // latest placed.mdl map (read by the placed-file cell) function init() { if (initialized) return; initialized = true; els = { trackingTab: document.getElementById('trackingTab'), + existingTab: document.getElementById('existingTab'), transmittalTab: document.getElementById('transmittalTab'), trackingPanel: document.getElementById('trackingPanel'), transmittalPanel: document.getElementById('transmittalPanel'), + mdlPanel: document.getElementById('mdlPanel'), trackingTree: document.getElementById('trackingTree'), transmittalTree: document.getElementById('transmittalTree'), + mdlTree: document.getElementById('mdlTree'), + loadMdlBtn: document.getElementById('loadMdlBtn'), addTrackingRootBtn: document.getElementById('addTrackingRootBtn'), addPartyBtn: document.getElementById('addPartyBtn'), stats: document.getElementById('classifyStats'), }; els.trackingTab.addEventListener('click', function () { showTab('tracking'); }); + if (els.existingTab) els.existingTab.addEventListener('click', function () { showTab('existing'); }); els.transmittalTab.addEventListener('click', function () { showTab('transmittal'); }); + if (els.loadMdlBtn) els.loadMdlBtn.addEventListener('click', loadMdl); els.addTrackingRootBtn.addEventListener('click', function () { var name = prompt('Root folder name (a tracking-number segment, e.g. "ACME-PROJ").\n' + 'Brace patterns expand: BMB-{PM,EL}-{0001-0002,0005}_A (IFR)', ''); @@ -10377,28 +10858,33 @@ X.B(E,Y);return E}return J}()) } // One pass: group files by the node they're placed in, per axis. function buildPlaced(files) { - var c = C(), byT = {}, byX = {}; + var c = C(), byT = {}, byX = {}, byM = {}; files.forEach(function (f) { var a = c.getAssignment(c.srcKeyForFile(f)); if (!a) return; if (a.trackingNodeId) (byT[a.trackingNodeId] = byT[a.trackingNodeId] || []).push(f); if (a.transmittalNodeId) (byX[a.transmittalNodeId] = byX[a.transmittalNodeId] || []).push(f); + if (a.mdlNodeId) (byM[a.mdlNodeId] = byM[a.mdlNodeId] || []).push(f); }); - return { tracking: byT, transmittal: byX }; + return { tracking: byT, transmittal: byX, mdl: byM }; } function showTab(which) { - var t = which === 'transmittal'; - currentTab = t ? 'transmittal' : 'tracking'; - els.trackingTab.classList.toggle('active', !t); - els.transmittalTab.classList.toggle('active', t); - els.trackingPanel.hidden = t; - els.transmittalPanel.hidden = !t; - // The "Hide Assigned" filter on the source tree is per-axis, so the - // visible set changes with the active tab — re-render the left tree. - if (window.app.modules.tree && window.app.modules.tree.render) window.app.modules.tree.render(); + currentTab = (which === 'transmittal' || which === 'existing') ? which : 'tracking'; + els.trackingTab.classList.toggle('active', currentTab === 'tracking'); + if (els.existingTab) els.existingTab.classList.toggle('active', currentTab === 'existing'); + els.transmittalTab.classList.toggle('active', currentTab === 'transmittal'); + els.trackingPanel.hidden = currentTab !== 'tracking'; + if (els.mdlPanel) els.mdlPanel.hidden = currentTab !== 'existing'; + els.transmittalPanel.hidden = currentTab !== 'transmittal'; + render(); + // The source-tree Show filters are per-axis, so the visible set changes + // with the active tab — re-render the left tree. + reRenderSource(); } - function activeAxis() { return currentTab === 'transmittal' ? 'transmittal' : 'tracking'; } + // The active axis is the catalog ('mdl') on the "By existing" tab, else the tab's. + function activeAxis() { return currentTab === 'existing' ? 'mdl' : (currentTab === 'transmittal' ? 'transmittal' : 'tracking'); } + function reRenderSource() { if (window.app.modules.tree && window.app.modules.tree.render) window.app.modules.tree.render(); } // Expand a brace pattern into folder names and create them (confirming a // multi-create first). parentId null = root folders. See expandFolderPattern. @@ -10423,6 +10909,7 @@ X.B(E,Y);return E}return J}()) var placed = buildPlaced(files); renderTrackingInto(els.trackingTree, C().getTrackingTree(), placed.tracking); renderTransmittalInto(els.transmittalTree, C().getTransmittalTree(), placed.transmittal); + renderMdlInto(placed.mdl); renderStats(files); } @@ -10756,6 +11243,184 @@ X.B(E,Y);return E}return J}()) return form; } + // ── By MDL (deliverables as drop targets via the shared seltable) ─────── + function renderMdlInto(placedMdl) { + mdlPlaced = placedMdl || {}; + if (!C().getMdlList().length) { + mdlTable = null; + els.mdlTree.textContent = ''; + els.mdlTree.appendChild(el('div', 'target-empty', 'Nothing loaded yet — “Load…”, tick the directories to scan, and their existing files + MDL deliverables appear here (one row per tracking number, latest revision).')); + return; + } + ensureMdlTable(); + mdlTable.renderBody(); + } + function ensureMdlTable() { + if (mdlTable) return mdlTable; + var c = C(); + // One column per configured tracking-number field (split positionally), + // then Title, MDL (✓), Archive revisions (informational), and the editable + // classifier-local Revision. Each column has its own autofilter. + var cols = c.getTrackingFields().map(function (f, i) { + return { key: 'f' + i, title: f.name, get: function (r) { return (r.trackingNumber || '').split('-')[i] || ''; } }; + }); + cols.push({ key: 'title', title: 'Title', get: function (r) { return r.title || ''; } }); + cols.push({ key: 'mdl', title: 'MDL', cls: 'catalog-mdl', get: function (r) { return r.inMdl ? '✓' : ''; } }); + cols.push({ key: 'latest', title: 'Latest rev', get: function (r) { return latestRevOf(r.archiveRevisions); } }); + cols.push({ + key: 'rev', title: 'Revision', cls: 'mdl-rev', get: function (r) { return r.revisionCell; }, + render: function (r, td) { + var inp = document.createElement('input'); + inp.type = 'text'; inp.className = 'mdl-rev__input'; inp.value = r.revisionCell || ''; + inp.placeholder = 'A (IFR)'; inp.setAttribute('data-no-select', ''); + inp.addEventListener('change', function () { c.setRevisionCell(r.id, inp.value.trim()); }); + td.appendChild(inp); + }, + }); + mdlTable = window.app.modules.seltable.create({ + container: els.mdlTree, + extraTitle: 'Files', + rows: function () { return c.getMdlList(); }, + rowId: function (r) { return r.id; }, + columns: cols, + onRowDrop: function (rowId, keys) { c.place(keys, rowId, 'mdl'); }, + onActivate: function (ids) { + if (!ids.length) return; + var v = prompt('Set the revision on ' + ids.length + ' selected row(s) (e.g. "A (IFR)"):', ''); + if (v != null) c.setRevisionCells(ids, v.trim()); + }, + rowExtra: function (r, td) { renderMdlPlaced(r, td); }, + }); + mdlTable.render(); + return mdlTable; + } + function renderMdlPlaced(row, td) { + var c = C(), files = mdlPlaced[row.id] || []; + files.forEach(function (f) { + var d = c.deriveTarget(f); + var a = c.getAssignment(d.key) || {}; + var line = el('div', 'tfile' + (d.errors.length ? ' tfile--err' : '')); + line.dataset.key = d.key; line.draggable = true; + line.addEventListener('dragstart', function (e) { window.app.modules.dnd.setDrag([d.key], e); }); + var nm = el('span', 'mdlfile__name', d.filename || '(set a revision)'); + nm.title = 'from ' + f.originalFilename + (f.extension ? '.' + f.extension : ''); + line.appendChild(nm); + var tgl = el('button', 'tnode__act', a.titleFromDeliverable === false ? 'Title: file' : 'Title: MDL'); + tgl.title = 'Use the deliverable’s title or the file’s own'; + tgl.addEventListener('click', function () { c.setTitleFromDeliverable(d.key, a.titleFromDeliverable === false); }); + line.appendChild(tgl); + var rm = el('button', 'tnode__act tfile__remove', '✕'); + rm.title = 'Remove from this deliverable'; + rm.addEventListener('click', function () { c.place([d.key], null, 'mdl'); }); + line.appendChild(rm); + td.appendChild(line); + }); + } + + // Load the catalog: "Load…" opens a multi-select directory tree (scoped to + // the served context); every ticked directory is walked recursively into the + // union of existing files + MDL deliverables, deduped by tracking number to + // one row at the latest revision. Writes/alters nothing — the revision cell + // is classifier-local and starts blank. + function isRowYaml(nm) { return /\.yaml$/i.test(nm) && nm !== 'table.yaml' && nm !== 'form.yaml'; } + + // The newest combined " ()" string in a set, by revision token. + function latestRevOf(revs) { + var best = null, bestTok = null; + (revs || []).forEach(function (r) { + var tok = String(r).replace(/\s*\([^)]*\)\s*$/, '').trim(); // "A (IFR)" → "A" + if (best == null || window.zddc.compareRevisions(tok, bestTok) > 0) { best = r; bestTok = tok; } + }); + return best || ''; + } + + // Where is the classifier served? Decides the directory-tree roots. + // 'local' → offline (file://), pick a folder. + // 'all' → standalone /_apps/classifier.html, root at every accessible project. + // {one:p} → under /…, root at just that project. + function detectScope(pathname, hasSource, protocol) { + if (!hasSource || protocol === 'file:') return 'local'; + if (/^\/_apps\//.test(pathname || '')) return 'all'; + var seg = (String(pathname || '').split('/').filter(Boolean)[0]) || ''; + return seg ? { one: seg } : 'all'; + } + async function buildRoots() { + var src = window.zddc && window.zddc.source; + var scope = detectScope(location.pathname, !!src, location.protocol); + if (scope === 'local') { + if (!window.showDirectoryPicker) { window.zddc.toast('Loading a local folder needs the File System Access API (Chromium).', 'error'); return null; } + try { var dir = await window.showDirectoryPicker({ mode: 'read' }); return [{ label: dir.name || 'Selected folder', handle: dir }]; } + catch (e) { if (e.name !== 'AbortError') window.zddc.toast('Could not open the folder — ' + (e.message || e), 'error'); return null; } + } + function archiveOf(rel) { + if (rel.charAt(rel.length - 1) !== '/') rel += '/'; + return new src.HttpDirectoryHandle(new URL(rel + 'archive/', location.origin).href, 'archive'); + } + if (scope === 'all') { + var projects = await window.app.modules.copy.fetchAccessProjects(); + if (projects == null) { window.zddc.toast('Could not load your projects from the server.', 'error'); return null; } + if (!projects.length) { window.zddc.toast('No projects you can access on this server.', 'warning'); return null; } + return projects.map(function (p) { return { label: (p.title ? p.name + ' — ' + p.title : p.name), handle: archiveOf(p.url || ('/' + p.name + '/')) }; }); + } + return [{ label: scope.one, handle: archiveOf('/' + scope.one + '/') }]; + } + async function loadMdl() { + var roots = await buildRoots(); + if (!roots) return; + var picked = await window.app.modules.dirPicker.pick(roots); + if (!picked || !picked.length) return; + var byTn = Object.create(null); + function ensure(tn) { return byTn[tn] || (byTn[tn] = { tracking: tn, title: '', inMdl: false, party: '', revs: Object.create(null) }); } + window.zddc.toast('Scanning selected directories…', 'info', { durationMs: 4000 }); + try { for (var i = 0; i < picked.length; i++) await walkDirInto(picked[i], ensure); } + catch (e) { window.zddc.toast('Reading the directories failed — ' + (e.message || e), 'error'); return; } + var rows = Object.keys(byTn).map(function (tn) { + var x = byTn[tn]; + return { id: tn, party: x.party, trackingNumber: tn, title: x.title, inMdl: x.inMdl, archiveRevisions: Object.keys(x.revs).sort(), revisionCell: '' }; + }); + finishLoad(rows); + } + // Walk a ticked directory recursively. A dir named "mdl" (or the ticked dir + // itself being an mdl folder) yields *.yaml deliverables → inMdl + title; + // every other ZDDC-named file is an archive revision of its tracking number. + async function walkDirInto(dirH, ensure) { + var party = (dirH.name && String(dirH.name).replace(/\/$/, '')) || ''; + if (party === 'mdl') return readMdlYamls(dirH, ensure); + for await (var entry of dirH.values()) { + var nm = String(entry.name).replace(/\/$/, ''); + if (entry.kind === 'directory') { + if (nm.charAt(0) === '.' || nm.charAt(0) === '_' || nm === 'rsk') continue; + var child = entry.getDirectoryHandle ? entry : await dirH.getDirectoryHandle(nm); + if (nm === 'mdl') await readMdlYamls(child, ensure); + else await walkDirInto(child, ensure); + } else { + var p = window.zddc.parseFilename(nm); + if (p && p.valid && p.trackingNumber) { + var row = ensure(p.trackingNumber); + if (!row.title) row.title = p.title || ''; + if (!row.party) row.party = party; + row.revs[(p.revision + (p.status ? ' (' + p.status + ')' : '')).trim()] = true; + } + } + } + } + async function readMdlYamls(mdlH, ensure) { + for await (var ye of mdlH.values()) { + var ynm = String(ye.name).replace(/\/$/, ''); + if (ye.kind !== 'file' || !isRowYaml(ynm)) continue; + var obj = null; try { obj = window.jsyaml.load(await (await ye.getFile()).text()); } catch (_) { /* skip */ } + var row = ensure(ynm.replace(/\.yaml$/i, '')); + row.inMdl = true; if (!row.title && obj && obj.title) row.title = obj.title; + } + } + function finishLoad(rows) { + C().setMdlList(rows); + showTab('existing'); + window.zddc.toast(rows.length + ? ('Catalog: ' + rows.length + ' tracking number' + (rows.length === 1 ? '' : 's') + ' from the selected directories. Drag files on, set revisions.') + : 'No files or deliverables in the selected directories.', rows.length ? 'success' : 'warning'); + } + // ── events ───────────────────────────────────────────────────────────── function closestNodeId(target) { var n = target.closest('[data-id]'); @@ -10957,7 +11622,10 @@ X.B(E,Y);return E}return J}()) function reveal(key) { var a = C().getAssignment(key); if (!a) return; - if (a.trackingNodeId) { + if (a.mdlNodeId) { + showTab('existing'); + if (mdlTable) { mdlTable.renderBody(); } + } else if (a.trackingNodeId) { showTab('tracking'); collapsed = {}; render(); flashNode(els.trackingTree, a.trackingNodeId); } else if (a.transmittalNodeId) { @@ -10981,6 +11649,10 @@ X.B(E,Y);return E}return J}()) activeAxis: activeAxis, setNameFilter: setNameFilter, reveal: reveal, + // test seams (pure) + _detectScope: detectScope, + _latestRevOf: latestRevOf, + _walkDirInto: walkDirInto, }; })(); @@ -11404,6 +12076,9 @@ X.B(E,Y);return E}return J}()) audit: audit, readyCount: readyCount, chooseOutput: chooseOutput, + // shared with the MDL flow + fetchAccessProjects: fetchAccessProjects, + chooseProject: chooseProject, // test/advanced seams plan: plan, conflictsIn: conflictsIn, diff --git a/zddc/internal/apps/embedded/index.html b/zddc/internal/apps/embedded/index.html index 322b0da..6da181d 100644 --- a/zddc/internal/apps/embedded/index.html +++ b/zddc/internal/apps/embedded/index.html @@ -1778,7 +1778,7 @@ body {
ZDDC - v0.0.27-beta · 2026-06-11 14:40:24 · bc762a7 + v0.0.27-beta · 2026-06-12 02:15:36 · 93ed0d3
diff --git a/zddc/internal/apps/embedded/transmittal.html b/zddc/internal/apps/embedded/transmittal.html index 690aec0..32c06d6 100644 --- a/zddc/internal/apps/embedded/transmittal.html +++ b/zddc/internal/apps/embedded/transmittal.html @@ -2770,7 +2770,7 @@ dialog.modal--narrow {
ZDDC Transmittal - v0.0.27-beta · 2026-06-11 14:40:24 · bc762a7 + v0.0.27-beta · 2026-06-12 02:15:36 · 93ed0d3
JavaScript not available