19 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| b5b3c92905 |
feat(shared): cap.js client helpers for permission gating
Three small helpers under window.zddc.cap, wired into every tool's
build:
cap.at(path) — Promise<AccessView|null>. Fetches
/.profile/access?path=<urlpath> and
memoises per-path for the session.
Used by tools to gate top-of-page
affordances on path_verbs / path_is_admin
/ path_can_elevate_grant.
cap.has(node, verb) — boolean. Reads the listing entry's
verbs string for the named verb.
Falls back to node.writable for 'w'
when verbs is absent (offline FS-API
listings or pre-promotion clients).
cap.handleForbidden(resp, — parses a 403 response's JSON body for
opts) missing_verb and renders an error
toast. When opts.path is supplied AND
the path-scoped access view reports
path_can_elevate_grant covering the
missing verb, the toast appends an
"Elevate" button that flips the
elevation cookie and reloads.
Browse loader.js + tree.js carry the new verbs field through to the
node objects so context-menu gating can call cap.has(node, 'w'|'d')
without changing the legacy node.writable contract. New CSS rule
.zddc-toast__action styles the inline Elevate button.
Concatenation order: cap.js comes after toast.js + elevation.js so
the dependencies (window.zddc.toast, window.zddc.elevation) are
present at module-load time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| bdd14609d1 |
build: simplify to stable + exact-version (drop alpha/beta as public concepts)
Releases publish only two things per tool now: a current-stable
canonical symlink and an immutable per-version file. No more channel
mirrors (_stable/_beta/_alpha) and no more partial-version pins
(_v<X.Y>, _v<X>) — those were debt from a release model that never
matched the project's actual usage.
The `./build beta` verb stays, but narrowed: it's an internal SHA
snapshot for the BMC dev chart pipeline (chart's appVersion pins to
"<X.Y.Z>-beta-<sha>" and the chart Dockerfile fetches that SHA from
git). No public artifact on /srv/zddc/releases/. The embedded/* +
chore commit produced by `./build beta` is the actual snapshot.
`./build alpha` is removed entirely.
build/build-lib.sh:
- Drop alpha verb; narrow beta verb to embedded regen + chore commit
- promote_release: stable cut writes <tool>_v<X.Y.Z>.html + <tool>.html
symlink + <tool>.html.sig companion symlink; beta is a no-op
- promote_zddc_server: same shape — per-version binary +
per-platform canonical symlink (zddc-server_<plat>) + .sig symlink
- write_zddc_server_stub: singular; emits per-version stubs +
one canonical zddc-server.html for current stable
- Delete _promote_channel, verify_channel_links, _channel_is_active
- Seed-from-live now copies only per-version files + .sig + pubkey.pem
(the canonical symlinks get rewritten by this cut; old layout files
get cleaned by deploy's --delete-after)
- build_releases_index: dropdown simplified to "latest stable +
pinned versions"; channels-explainer section removed; tool cards +
CTA URLs point at canonical <tool>.html / zddc-server_<plat>;
composer emits "stable" sentinel for `apps:` entries
- Fix the acl:{allow:[...]} footgun in the apps_pubkey example
apps.go:
- isValidChannelOrVersion: accept only "stable" + exact X.Y.Z
(drop alpha/beta and partial pins v0.0/v0)
- normalizeChannel: same
- Resolve URL composition: stable → canonical <prefix>/<app>.html
(no _stable_ suffix), exact-version → <prefix>/<app>_v<X.Y.Z>.html
- Tests rewritten to match (beta/alpha replaced with v0.0.4 / stable;
a new TestParseSpec_RejectsLegacyChannelsAndPartialPins locks in
that the removed forms now error)
browse/build.sh: gate promote_release on $is_release like every other
tool's build.sh (longstanding inconsistency that errored under the new
promote_release case-statement).
freshen-channel: deleted (no channels to freshen).
Net: -254 lines, all green on full `go test ./...`. Dev build verified
via `./build` (no-arg) — new label format "v<next>-dev · <ts> · <sha>".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 19566360a6 |
ui: fix admin-mode frame; drop project-stage strip
Three UI cleanups against the admin/browse chrome.
Red admin-mode frame (shared/elevation.css)
Was: body { outline: 3px ... ; outline-offset: -3px } — an outline
doesn't reflow content, so in tools that butt their content to the
viewport edge (browse split-pane, archive grid) the frame painted
on top of the first 3px of content.
Now: body.is-elevated::after { position:fixed; inset:0; border:3px;
pointer-events:none; z-index:9200 }. The frame lives in its own
fixed layer above all content, so it never overlaps or steals
clicks; content layout is unchanged.
Project-stage strip (Archive · Working · Staging · Reviewing)
Low-value chrome. Removed entirely:
- delete shared/nav.js + shared/nav.css
- drop the include from every tool's build.sh
(browse, transmittal, form, archive, landing, tables, classifier)
- delete tests/nav.spec.js
- rebuild tables.html (the //go:embed'd baked-in copy)
Project navigation already happens through the directory tree in
browse and the URL bar; the strip duplicated breadcrumb information
without adding capability.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 690d185dc2 |
feat: reviewing/ lifecycle — Plan Review endpoint, virtual received window, browse context-menu workflows
Two layers shipped together since the second builds on the first. LAYER 1 — reviewing/ + Plan Review scaffolding - reviewing/ is now a real folder under each project, populated by the Plan Review composite endpoint. The old reviewing/ virtual aggregator handler is retired. - POST /<project>/archive/<party>/received/<tracking>/ with X-ZDDC-Op: plan-review scaffolds physical workflow folders under reviewing_root and staging_root, each carrying .zddc.received_path pointing back at the canonical submittal. Idempotent re-runs match by received_path and re-converge the ACL. - Virtual received window: when listing or writing under <workflow>/received/, the server resolves through the canonical archive/<party>/received/<tracking>/ via the workflow's .zddc.received_path. Writes get rewritten to <workflow>/<base>+C<n><suffix> so review comments land in the workflow folder and never touch the WORM archive. - Cascade defaults declare on_plan_review per project so the reviewing_root and staging_root are configurable. LAYER 2 — browse context-menu workflows - Accept Transmittal: right-click a transmittal folder in archive/<party>/incoming/ → validates ZDDC folder + filename conformance, atomic-renames the folder to archive/<party>/received/<tracking>/ (WORM zone), and optionally chains into Plan Review in the same composite request. Re-acceptance with a different revision merges file-by-file; WORM forbids overwrite of an existing filename. - Stage / Unstage: right-click files in working/<…>/ → "Stage to…" with picker of existing staging transmittal folders + inline "New transmittal folder…" create; right-click files in staging/<…>/ → "Unstage to working/" defaulting to the user's working/<email>/ home. Reuses the file-API move primitive. - Create Transmittal folder: right-click the staging/ pane → prompts for a ZDDC-conforming folder name with live validation; mkdir, then navigate to the new folder URL where the transmittal tool serves the editor. - Supporting infrastructure: new CanonicalFolderAt cascade lookup + X-ZDDC-Canonical-Folder response header so the browse SPA can scope-gate menu items without re-implementing the cascade client-side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 94b2e29448 |
feat(browse): SPA overhaul — context menu, YAML editor, icons, hovercard, deep links, autofilter
Major upgrade to the browse tool's UX, plus a few shared modules other tools can adopt. User-facing: - Right-click context menu on tree rows AND empty pane space. Traditional file-manager grouping (Open / Download / New / Rename-Delete / Copy / Tree ops / View). Items stay visible but disabled when not applicable so muscle memory carries. Generic shared/context-menu.js framework supports normal items, toggles, submenus, separators, danger styling. - YAML editor for .yaml / .yml / .zddc files (CodeMirror 5 vendored at shared/vendor/codemirror-yaml.min.*). js-yaml lint on every change for parse errors. For .zddc cascade files, an additional schema-aware lint pass flags unknown keys, bad enum values, and wrong types. - Per-row drag-drop upload using webkitGetAsEntry (folder uploads work recursively). Per-row drop indicator; doc-level overlay still fires for blank-space drops at drop_target scopes. - New folder / New markdown file context-menu items (server mode). Rename + Delete with native confirm() dialog. File-API helpers removeNode / renameNode use the existing PUT/POST/DELETE endpoints. - Hover info card with the row's full metadata (ZDDC fields + filesystem info + path/URL). Interactive — mouse into it, drag-select text, Ctrl/Cmd-C or right-click → Copy. 200ms grace before dismiss. - Autofilter input at the top of the tree pane. Same grammar as archive's column filters (zddc.filter.parse / matches). Filters files; folders without matches collapse out. Non-matching folders force-open visually when descendants match, without mutating the user's actual expand state. - Two-line ZDDC label: title-first, tracking/rev/status as monospace meta below. Icon column anchors to the title line. Chevron is a Lucide outline `chevron-right` SVG, rotated 90° on `.expanded`. - File-type Lucide icon sprite (shared/icons.js — 16 outline glyphs, ~5 KB). PDF / Word / Spreadsheet / Slides / Image / Video / Audio / CAD / Web / Config / Code / Archive get distinct icons; folders tinted with --primary. - Header wraps gracefully at narrow viewports (shared/base.css flex-wrap + title min-width:0 ellipsis). Body becomes flex column in browse so a wrapping header doesn't break #appMain height. - Markdown editor opens in WYSIWYG mode by default. YAML front-matter + TOC sidebar reworked: flexbox layout (single visible resizer between FM and TOC), both bodies overflow:auto for X+Y scrollbars. - `?file=<path>` deep links open browse pre-positioned at a specific file. Multi-segment paths walk into subdirectories on the way. Auto-flips Show hidden when a segment is dot/underscore-prefixed. - Refresh + show-hidden toggle preserve expansion / selection / preview pinning. Path-keyed snapshot survives a re-fetched listing. - "Add Local Directory" → "Use Local Directory" across the four tools that have it (browse, archive, classifier, +transmittal comment). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| fb27e47866 |
fix(browse): bundle shared/zddc-source.js so downloadConverted is available
The markdown editor's DOCX/HTML/PDF download buttons silently no-op'd because the gate `typeof window.zddc.source.downloadConverted === 'function'` always failed: browse rolls its own server-mode detection (state.source === 'server' + node.url) and never needed the shared/ zddc-source.js polyfill before. The new download helper lives on window.zddc.source, so browse needs to bundle it. Adds ../shared/zddc-source.js to browse/build.sh's concat_files list, right after preview-lib.js. Bundle gains ~12 KB; tools that don't need the polyfill (browse doesn't use HttpDirectoryHandle directly) pay a small footprint cost in exchange for getting the helper. |
|||
| 141fef88fb |
feat(browse): "Download (zip)" — pull the current directory's subtree as a zip
A "⤓ Download (zip)" button in the browse toolbar (shown once a
directory is loaded) downloads the directory you're currently
viewing — and everything under it you're allowed to see — as a single
.zip. Navigate into a subfolder first to grab just that subtree.
- Server mode: an <a download> at "<currentPath>?zip=1" — zddc-server
streams the ACL-filtered zip (see the previous commit), nothing held
in the browser.
- Offline (file://) mode: new browse/js/download.js walks the picked
folder with the FS-Access API in two passes — metadata first (so it
can confirm() before loading >~2000 files / ~500 MB into memory),
then bytes — bundles with the already-vendored JSZip, and triggers a
blob download. Hidden entries (".":/"_"-prefixed) are skipped, the
zip's top level is "<folderName>/…" so it unpacks tidily, and the
status bar shows progress.
Wired in browse/js/events.js (button click + show/hide alongside the
refresh button); concatenated into browse/build.sh; ARCHITECTURE.md +
AGENTS.md note the ?zip=1 endpoint and the button.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 2dc2d032a0 |
feat(archive,browse): treat .zip transmittal folders as folders + shared zip adapter
New shared/zip-source.js: a ZipDirectoryHandle / ZipFileHandle pair that exposes a JSZip instance behind the File-System-Access surface (values/entries/keys, getDirectoryHandle/getFileHandle, getFile) — read-only, with a zip-slip guard. Mirrors shared/zddc-source.js's HTTP polyfill. Wired into archive's and browse's build.sh (both already bundle JSZip). archive: a .zip whose name minus ".zip" parses as a transmittal-folder name is now scanned as that transmittal folder. Offline, the zip is opened in the browser (ZipDirectoryHandle) and its members enumerated exactly like an uncompressed folder's files — table/export/hash paths are unchanged (they go through file.handle.getFile()). Online, the scanner recurses into the server's "<…>.zip/" virtual-directory listing, so members come back as "<…>.zip/<member>" URLs the server extracts on demand — no whole-zip download. browse: the offline (file://) zip path is migrated onto the shared adapter — expanding a .zip now opens it as a ZipDirectoryHandle and its members become ordinary dir/file nodes handled by the normal fetchFsChildren path (nested zips fall out by recursion). The bespoke flat-entry walker (loadZipChildren / setZipDirChildren / zipEntries / zipParentId / zipPath / _zipSyntheticDir) is gone — one zip implementation repo-wide. Markdown members inside a zip are flagged read-only (the ZipFileHandle refuses createWritable; server "<…>.zip/" URLs 405 on PUT). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 4af0d8ca7c |
feat(browse): drag-drop upload into working/staging/incoming
Drop files anywhere on a browse page; if the current scope is inside
a working/, staging/, or incoming/ subtree the files are PUT to the
current directory via the existing file API. Per-file ACL is enforced
server-side (authorizeAction); a 403 surfaces as a per-file error
toast and the rest of the batch proceeds.
UX:
- dragenter → semi-transparent overlay with a dashed-border panel
showing the destination path. Hides immediately on dragleave or
drop.
- drop → "Uploading N files…" toast, then per-file failure toasts
inline, then a summary toast (success / partial / all-failed).
- listing auto-refreshes after the batch so new files appear in
the tree without a manual reload.
Scope:
- upload-eligible paths are matched by /\/(working|staging|incoming)
(\/|$)/i — same convention as the new grid-mode URL token.
- 256 MiB per-file cap (UPLOAD_MAX_BYTES) since browse's single-
body PUT loads the file as a Blob in the tab; larger uploads
should use a dedicated client.
- Outside the upload-eligible set the overlay never appears; drops
are silently ignored (drag effect = none).
Sequential uploads keep progress predictable; parallel batching can
land later if needed. The module hooks document-level dragenter/leave
/over/drop so it works regardless of which pane the user drags over.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 6260aa4860 |
feat(typography): bake IBM Plex Sans + Source Serif 4 into every tool
System-default font stack ('-apple-system, BlinkMacSystemFont, Segoe UI,
…') is the textbook generic admin-tool look. The tools have a real point
of view (engineering documents, traceability, immutability); the
typography should reflect that.
Picks:
--font → IBM Plex Sans (400 + 600). UI body text. Distinctive
engineering sans with tabular nums and proper figures.
--font-display → Source Serif 4 (600). Headings, page titles,
.app-header__title. Reads as "document" not "UI label."
--font-mono → unchanged. Platform mono fonts are already excellent
and engineering tools rarely benefit from a custom mono.
Wiring:
- Raw .woff2 files live in shared/fonts/ (~60 KB total, latin subset,
SIL OFL 1.1 — both families)
- shared/fonts.css is base64-inlined data URIs for those three fonts
(~80 KB after b64 overhead). Generated once from the snippet in
shared/fonts/README.md.
- Every tool's build.sh prepends shared/fonts.css before shared/base.css
so @font-face is parsed before any rule references the family names.
- Headings (h1-h6) and .app-header__title now use var(--font-display);
.app-header__title bumped 17→18px and letter-spacing reset since the
serif doesn't need the original sans-text tightening.
- table/code/.tabular-nums get font-variant-numeric: tabular-nums so
tracking-number columns align vertically.
"Ship the record player with the record": zero CDN dependency at render
time. Tools render identically offline and online. Per-tool dist sizes
grew by ~80 KB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 7d4d2dc9a2 |
feat(browse): two-pane shell + markdown plugin + grid mode (Phases A/B/C/D)
Reshape browse from "tree-as-table with popup preview" into a unified
file-experience tool with three layered behaviors:
Phase A — Two-pane shell
Phase B — Markdown plugin (Toast UI inline)
Phase C — Grid mode (classifier workflow)
Phase D — Deprecation banners on standalone classifier + mdedit
= Phase A: two-pane shell + lightweight preview plugins =
Browse's table view becomes a tree-pane on the left + preview-pane on
the right with a draggable resizer. Click a folder → expand inline.
Click a file → render in the right pane. The previous popup window
becomes an explicit "⤴ Pop out" button in the right-pane header for
users with a second monitor.
Preview rendering reuses shared/preview-lib.js (PDF iframe, image
<img>, TIFF, ZIP listing, text <pre>). Unknown types show a download
link. browse/js/preview.js refactored into renderInline (default) +
renderInPopup (Pop out button); both share the same plugin
dispatch logic.
Filter rows were already removed earlier this session. Sort columns
likewise — the tree is alphabetical by default; the underlying
setSort API still exists for future re-introduction.
= Phase B: markdown plugin =
New browse/js/preview-markdown.js: when a .md or .markdown file is
clicked, the right pane mounts a Toast UI editor (initial-value =
file contents) with a small toolbar containing Save + dirty indicator
+ status text. Save sends PUT through the file API for server-mode
files; non-server sources are read-only for now (deferred to a
follow-up that wires zddc-source.js writes too). Ctrl+S / Cmd+S
inside the editor saves.
Toast UI Editor (~700 KB JS + ~160 KB CSS) was previously bundled
only in mdedit/vendor/. Moved to shared/vendor/ so browse and mdedit
both pull from one location.
= Phase C: grid mode =
View-mode toggle [Browse | Grid] in the toolbar. Grid mode loads the
classifier tool as an iframe scoped to the current directory (server
mode at working/staging/incoming locations) — classifier's full
bulk-rename workflow without leaving browse. v1 implementation; a
future iteration could bundle classifier's modules directly into
browse for tighter integration. Hostile cases (file:// origin, paths
outside working/staging/incoming) show a friendly explanation
instead of a blank iframe.
new browse/js/grid.js handles the activation logic.
= Phase D: deprecation banners =
mdedit and classifier standalones gain a "this tool is being absorbed
into Browse" advisory banner. Both standalones remain fully
functional and continue to ship — they're useful for offline single-
file editing and air-gapped environments. The banner just points
users toward the unified browse experience.
= Files =
+ browse/js/preview-markdown.js (markdown plugin)
+ browse/js/grid.js (grid-mode plugin)
M browse/template.html (two-pane layout, view toggle, banners)
M browse/css/tree.css (two-pane CSS, replaces table styles)
M browse/js/init.js (state additions: selectedId, viewMode)
M browse/js/tree.js (rowHtml: <tr>+<td> → <div>)
M browse/js/preview.js (renderInline / renderInPopup split)
M browse/js/events.js (toggle wiring, resizer, click handlers
adapted from <table> to <div>)
M browse/build.sh (Toast UI vendor + new modules)
R mdedit/vendor/toastui-* → shared/vendor/ (one bundle, two tools)
M mdedit/build.sh (paths)
M mdedit/template.html (deprecation banner)
M classifier/template.html (deprecation banner)
M tests/browse.spec.js (selectors updated for new layout +
new "click file → preview" test)
Bundle sizes after this commit:
browse: ~1020 KB (was ~290 KB; added Toast UI ~700 KB)
classifier: ~1470 KB (unchanged from prior baseline)
mdedit: ~2140 KB (unchanged; vendor location moved but not added)
What's deferred:
- TOC + front-matter pane in browse's markdown plugin (mdedit has
these; browse v1 uses just the editor).
- FS-API writes from browse's markdown plugin (server PUT works).
- Classifier modules bundled directly into browse (v1 uses iframe).
- Sort UI in the new tree (model still supports it; no widget yet).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| d6206b03e7 |
feat(shared): bake xlsx + utif + jszip + docx-preview into every tool
Removes every runtime CDN load. The "ship the record player with the
record" philosophy: a downloaded .html file works offline against any
file the user can open, with no network dependency at runtime.
Newly vendored under shared/vendor/:
- xlsx.full.min.js (SheetJS, 928 KB) — XLSX/XLS preview
- utif.min.js (UTIF, 57 KB) — TIFF preview
Already there but now used by mdedit too:
- jszip.min.js, docx-preview.min.js
Call sites updated to drop the `await loadLibrary(URL)` pattern —
since the vendor JS is concatenated into the inline <script> at build
time, window.XLSX / window.JSZip / window.UTIF / window.docx are
available synchronously from page load.
Per-tool changes:
- archive/build.sh: +xlsx, +utif
- classifier/build.sh: +xlsx, +utif
- transmittal/build.sh: +xlsx, +utif
- mdedit/build.sh: +jszip, +docx-preview, +xlsx, +utif
(mdedit was the only tool not yet
bundling any of the preview deps)
- browse/build.sh: +utif
- archive/js/table.js, classifier/js/preview.js,
transmittal/js/files-preview.js, mdedit/js/file-tree.js (×2):
drop the `await loadLibrary('…cdn…')` lines.
- shared/preview-lib.js:
drop the loadLibrary(UTIF) / loadLibrary(JSZip) wrappers; assume
window.UTIF and window.JSZip are present.
Net bundle-size delta after baking:
archive: +990 KB → ~1.47 MB
browse: +57 KB → ~292 KB
classifier: +990 KB → ~1.43 MB
mdedit: +1100 KB → ~2.09 MB
transmittal: +990 KB → ~1.63 MB
Docs (AGENTS.md, ARCHITECTURE.md) updated: removed the "runtime CDN
loading exception" paragraph and the table row that flagged xlsx as
CDN-loaded.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 7fd96c7c78 |
feat(shared): clickable logo links every tool's header to project home
The .app-header__logo SVG was decorative on every tool. Web's strongest convention is "click logo → go home" — so users tapping it expecting that fallback got nothing. Now the logo is wrapped in an anchor whose href reflects the URL the page was loaded from: file:// → no wrap (no server home to point at) / → wrap, href=/ (deployment root) /index.html / /<tool>.html → wrap, href=/ (root, no project) /<project>/... → wrap, href=/<project> (project landing) The wrap happens client-side at DOMContentLoaded via shared/logo.js, loaded by every tool's build.sh after toast/nav. Idempotent — a template-supplied anchor or a second mount call is a no-op. The companion shared/logo.css adds a subtle hover/focus affordance (opacity 0.82, focus ring) so the logo reads as clickable without otherwise altering its visual weight. Tools opt out by setting window.zddc.logo.disabled = true before DOMContentLoaded (e.g. for deployments that pin the logo to an external destination). Five Playwright tests (tests/logo.spec.js) lock the contract: no-wrap on file://, href=/ at root, href=/<project> in project subtree, aria-label matches target, idempotent re-mount. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 7ced0395b6 |
feat(shared): lateral project-stage strip in every tool's header
Adds a thin nav strip directly under the app-header showing the four canonical lifecycle stages from the transmittal-workflow spec: archive · working · staging · reviewing. Each is a link to that stage's directory under the current project. Current stage is highlighted (bold + primary color, aria-current="page"). Strip mounts as a sibling of .app-header on DOMContentLoaded — no template changes needed in any tool. Render rules (shared/nav.js shouldRender): - location.protocol must be http: or https: (file:// has no project structure to navigate within) - a project segment must be detectable as the first path segment (when it isn't a tool HTML file like /index.html or /archive.html?projects=A,B). Multi-project view at the deployment root therefore shows no strip. Stage URL targets: - Archive → <project>/archive.html (project-root archive view) - Working → <project>/working/ (directory listing — mdedit auto-served) - Staging → <project>/staging/ (directory listing — transmittal auto-served) - Reviewing → <project>/reviewing/ (directory listing) Convention-driven, not probed: if a deployment doesn't have one of these folders the link returns 404. Operators on non-standard layouts can opt out by setting window.zddc.nav.disabled = true before DOMContentLoaded. This pairs with the previous landing-tool change (single-project click → <project>/archive.html). Together they give the user both URL-bar manipulation AND visible navigation across the four canonical project stages. Five Playwright tests in tests/nav.spec.js exercise: - non-render at deployment root - render + active stage on <project>/archive.html - render + active stage deep inside <project>/working/foo/mdedit.html - canonical link targets - mount position is sibling of .app-header Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 8ba029612e |
feat(shared): non-blocking toast helper available to every tool
Promote classifier's local toast (classifier/css/base.css + showToast
in classifier/js/excel.js) into shared/toast.{js,css}. Every tool's
build.sh now concatenates them, so window.zddc.toast(msg, level, opts)
is callable from any tool.
API:
window.zddc.toast('Saved.', 'success');
window.zddc.toast('Could not load: ' + err.message, 'error');
window.zddc.toast('Note', 'info', { durationMs: 3000 });
Levels: info (default) | success | warning | error. Single-toast
policy — a second call replaces the first. Click anywhere on the
toast to dismiss. ARIA: error → role=alert/aria-live=assertive,
others → role=status/aria-live=polite.
Class prefix is .zddc-toast (BEM-ish) to avoid colliding with any
tool-local .toast rules. Classifier's existing showToast now
delegates to window.zddc.toast — call sites in excel.js +
selection.js are unchanged. Classifier's local .toast CSS block
deleted in favor of the shared one.
This commit only EXPOSES the API. Replacing the ~25 alert() call
sites scattered across archive/transmittal/mdedit/classifier with
toast calls is left as follow-up — each alert needs per-call review
to decide if it's truly non-blocking.
Five Playwright tests in tests/toast.spec.js lock the contract:
API exposure, level mapping, ARIA roles, single-toast replace,
click-to-dismiss.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 22c142e45a |
chore(headers): standardize across all 7 tools
Bring every tool's header in line with archive's pattern: [logo] [title] [version] [Add Local Directory] [⟳] ............... [◐] [?] ------------- header-left --------------- ----- header-right - Changes per tool: * browse: rename "Select Directory" → "Add Local Directory"; add the red-non-stable wrap to the build label (was missing); add a help panel + bundle shared/help.js. * classifier: rename selectDirectoryBtn → addDirectoryBtn, refreshBtn → refreshHeaderBtn for consistency. Update all JS callers and welcome-screen copy to the new label. * mdedit: same id rename. Move the previously-in-pane refresh button into the header. Stop renaming the dir button to "Directory: <name>" once a folder is loaded — instead use the shared btn--subtle variant to de-emphasize while keeping the standard label. * transmittal: convert non-standard <div class="app-header"> with spacer/icons containers to <header class="app-header"> with the canonical header-left/header-right pair. Move the publish split- button into header-left (Transmittal-specific primary action). Remove dead .app-header__spacer/__icons/header-icon-btn CSS now that nothing references those classes. * landing, form: add help-btn + help-panel + bundle shared/help.js. Each panel is tool-specific (project picker docs for landing, schema-driven form docs for form). Cross-cutting: * shared/base.css: promote .btn--subtle from browse/css/tree.css so any tool with an online mode can de-emphasize Add Local Directory consistently. Verified all 7 tools in headless Chromium: header structure correct, build label red on non-stable cuts, help panel opens + closes via button + Esc. |
|||
| 582db6d86d |
feat(browse): vendored JSZip, SVG home icon, auto-filter rows
- Vendor JSZip locally (shared/vendor/jszip.min.js) and bundle into
the browse build instead of CDN-loading. Eliminates the failure
mode where ZIP rows can't expand because the CDN script doesn't
load (CSP, network, etc.). Tool now works fully offline.
- Replace the toolbar filter input + ext multi-select with two
spreadsheet-style auto-filter rows in <thead>:
- 📄 row: file-name filter + extension filter
- 📁 row: folder-name filter
Each input uses shared/zddc-filter syntax (substring/!negate/
^startsWith/$endsWith/regex/| or/space and).
- New visibility model with ancestor-of-match awareness:
- file matches keep their ancestor folders visible (path-to-hit)
- folder match keeps its descendants visible
- filters compose (file ∧ folder ∧ ext) so combinations narrow
Computed model-side; render walks only visible nodes.
- Replace 🏠 emoji breadcrumb-root with an inline outline-stroke SVG
that tints with currentColor.
|
|||
| 424bf8e769 |
feat(browse): Phase 2 — preview popup, ZIP expansion, ext filter, breadcrumbs
Bundles Phase 2 polish + the user-requested header/breadcrumb work:
- Breadcrumbs replacing the plain currentPath span. Server mode
renders linkified ancestor segments (each <a> navigates to that
directory; the browser fetches browse.html, the new instance
auto-loads the listing). FS-API mode renders the rootHandle name
as a non-link (no ancestor handles to navigate). Both prefix the
path with a 🏠 root icon. Trailing slash + bold-current segment
match common file-explorer conventions.
- Subdued 'Select Directory' button in server mode. Once browse is
serving a real directory listing, the local-folder switcher is
available but visually quiet (btn--subtle: transparent, muted
color). FS-API mode keeps the primary styling (it's how the user
got there). New btn--subtle CSS class added to browse's tree.css.
A refresh button (⟳) appears next to it in both modes; clicking
it re-fetches the current root listing.
- Header consistency: browse now matches archive's header layout
(refresh + help buttons in addition to theme on the right). Help
is a placeholder for future help dialog wiring.
- File preview popup. Click a file row → opens a popup window with
the file rendered. Plain types (PDF, HTML, image) load in
iframes; TIFF + ZIP listings via shared/preview-lib.js's
renderTiff / renderZipListing helpers; text via <pre>; unknown
types → 'click Download' placeholder. Modifier-click (ctrl/cmd/
shift) and middle-click still open the file in a new tab via the
underlying <a target=_blank>. Single popup window is reused
across multiple file clicks (matches archive's UX).
- ZIP inline expansion. .zip files have a chevron and act like
folders in the tree. First expand fetches the zip bytes
(server URL or FS handle or parent-zip read), parses with JSZip
(auto-loaded from CDN), and synthesizes the entry tree. Nested
directories within the zip lazy-expand on demand by re-walking
the cached entry list at the right path prefix. Click on a
zip-entry file opens the preview popup with bytes read from
JSZip. Recursive expand-all skips zip archives by design — they
can be very large, and explicit click-to-expand is safer.
- Extension multi-select filter. Toolbar now has a <select
multiple> populated with extensions present in the current
view. Filter is OR-of-selected; combined with the name filter
it's AND-of-both. Folders pass through (so expanding a folder
whose name doesn't match the ext filter still shows its file
children that do match).
|
|||
| fb13ff4fd8 |
feat(browse): generic directory listing tool — default at folder URLs
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 5s
A new HTML tool — browse — that lists the contents of any directory.
Designed for ZDDC archives but no ZDDC-specific filtering; just a
straight folder browser with expand/collapse, sort, and name filter.
Modes (auto-detected at page load):
- Online: when served by zddc-server at a folder URL, queries
the same URL with Accept: application/json to load the listing
and renders it. Auto-served as the default at any directory
under ZDDC_ROOT without an index.html (replacing the previous
minimal-HTML stub from directory.go).
- Local: 'Select Directory' button uses FileSystemAccessAPI to
pick any folder on disk; works in Chromium-based browsers.
Features (Phase 1 — what's in this commit):
- Tree view with lazy-loaded folders (children fetched on first
expand).
- Sort by name / size / extension / date (column header click).
- Filter by name substring (toolbar input).
- File click opens in a new tab — for server-backed pages,
routes through zddc-server's normal handler so .archive
redirects + apps cascade overrides + ACL all apply.
Phase 2 deferred:
- ZIP files inline expansion (treat archive entries as virtual
children).
- File preview popup (reuse shared/preview-lib.js).
- Extension multi-select filter.
Wiring:
- browse/ added to top-level ./build's per-tool list, embed
block, versions.txt, and the lockstep release commit + tag set.
All seven tools (archive, transmittal, classifier, mdedit,
landing, form, browse) advance together on stable cuts.
- shared/build-lib.sh: browse added to ZDDC_RELEASE_TOOLS and
verify_channel_links's per-tool loop.
- zddc/internal/apps/embed.go: //go:embed browse.html +
EmbeddedBytes("browse") case.
- zddc/internal/apps/availability.go: browse available at every
directory (same as archive).
- zddc/internal/apps/handler.go: MatchAppHTML routes
/<dir>/browse.html → 'browse'.
- zddc/internal/handler/directory.go: when a directory request
arrives with Accept: text/html and no index.html exists,
serve the embedded browse.html bytes (with a JSON-fallback
if the embedded slot is empty during bootstrap).
|