Commit graph

8 commits

Author SHA1 Message Date
677ac01b32 refactor(shared): consolidate empty-state into shared chrome (BEM)
Three tools (archive, browse, classifier) independently implemented
an empty-state pattern with three different CSS class naming
conventions and slightly different rules:

  archive:    .empty-state + .empty-state-content (BEM-less)
  browse:     .empty-state + .empty-state__inner  (BEM)
  classifier: .empty-state + .empty-state-content (BEM-less)

Same visual intent ("nothing's loaded yet — here's a welcome card
with instructions"), implemented three times with subtly different
spacing, no shared body styling for h2/p/ul/li, and incompatible
class names that prevented a future tool from copy-pasting the
pattern.

Promote a single consolidated rule set to shared/base.css using
BEM naming throughout:

  .empty-state                       — base (flex centered, padding)
  .empty-state--overlay              — modifier: position absolute,
                                        top 50px to clear app-header,
                                        z-index 10. Used by archive
                                        and classifier (their empty
                                        states sit OVER the main
                                        layout).
  .empty-state__inner                — content card (left-aligned,
                                        text-muted, max-width 640)
  .empty-state__inner--centered      — modifier: tighter max-width
                                        500, centered text, 2rem
                                        padding. Used by tools whose
                                        welcome screen reads as a
                                        centered card.
  .empty-state__inner h2/p/ul/ol/li  — typography defaults
  .empty-state__inner .note          — italic small-print
  .welcome-list                      — bullet list with left-aligned
                                        text + auto margins; safe to
                                        nest inside a centered card.

Per-tool changes:

  - archive/template.html, archive/js/app.js: rename
    .empty-state-content → .empty-state__inner empty-state__inner--centered;
    add .empty-state--overlay to the outer .empty-state container.
    Also the runtime-injected unsupported-browser markup in
    showUnsupportedBrowserMessage() and the showHttpErrorState
    selector.
  - classifier/template.html: same renames.
  - archive/css/layout.css + components.css: delete .empty-state*
    and .welcome-list rules (now in shared).
  - classifier/css/layout.css: same. Keep .empty-state.drag-over
    locally — classifier is the only tool whose empty state acts
    as a drop target.
  - browse/css/base.css: delete .empty-state* (shared covers it).
    browse's template was already using .empty-state__inner so no
    template change needed.

LOC: shared/base.css gains ~70 lines; per-tool overrides lose ~85
combined. Net -15. More importantly, future tools can reuse the
pattern by adding two divs and (optionally) the --centered or
--overlay modifiers; no copy-paste required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:21:07 -05:00
33ce3886f2 fix(form,browse): drop divergent local overrides of shared chrome
Two more half-contract leaks surfaced by sweeping shared chrome class
usage across tools:

1. form/ had its own design-token namespace (--color-primary,
   --color-bg, --color-text, --color-border, --color-bg-alt,
   --color-text-muted) defined nowhere but fallback'd to hardcoded
   hex values different from shared (#1e3a5f vs shared's #2a5a8a).
   Form's buttons, inputs, fieldsets, array-row borders, and status
   colors all rendered with subtly different palette than the rest
   of the suite.

   Form also redefined .btn, .btn-primary, and a (form-local)
   .btn-small class — the redefinition shadowed shared/base.css's
   button system entirely. Form's JS used 'btn btn-small' for the
   add/remove row buttons in form-array widgets.

   Fixes:
   - form/css/form.css: rename every --color-* reference to the
     matching shared token (--primary, --bg, --bg-secondary, --text,
     --text-muted, --border, --radius, --danger, --success).
   - form/css/form.css: delete the .btn / .btn-primary / .btn-small
     blocks entirely. Shared covers .btn / .btn-primary /
     .btn-secondary / .btn-sm / .btn-lg / .btn-link.
   - form/js/array.js: switch the row add/remove buttons to
     'btn btn-sm btn-secondary' so they pick up shared's sizing
     and outline variant.
   - tests/form-safety.spec.js: update the selector
     button.btn-small → button.btn-sm.

2. browse/ had .hidden { display: none !important; } — exact
   duplicate of shared/base.css's rule. Delete the redundant copy
   (left a one-line comment pointer in case anyone wonders why
   it's missing from the local sheet).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:16:23 -05:00
53eb58b90b refactor(browse): remove auto-filter rows from header
The two filter rows (📄 file/ext + 📁 folder) didn't really earn their
header real estate. Browse is for navigating directory structure;
ad-hoc filters across a tree of mixed file types and depths weren't
the right affordance, and the visual weight competed with the column
headers and the breadcrumb. Removed entirely:

  - template.html: dropped both <tr class="filter-row"> rows in <thead>,
    the related "Filter rows" help section, and the empty-state copy
    that mentioned the 📄/📁 rows.
  - init.js: dropped state.filters (file/folder/ext slots).
  - events.js: dropped the .column-filter[data-filter] input wiring.
  - tree.js: dropped recomputeVisibility() and the n.visible plumbing
    in visibleIds() and updateCount(). Render is now a straight depth-
    first walk over expanded subtrees; the count is just total rows.
    setFilter is removed from the public API.
  - css/tree.css: dropped .filter-row*, .filter-row__icon, and the
    browse-local .column-filter rules (.column-filter is also defined
    in shared/base.css for tools that still use it; that stays).

No test changes — tests/browse.spec.js never exercised filters.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:50:58 -05:00
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.
2026-05-04 07:49:17 -05:00
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.
2026-05-03 21:35:15 -05:00
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).
2026-05-03 20:39:49 -05:00
7caf3ecf3f fix(browse): listing fetch + row height + recursive expand/collapse
Three issues from initial v0.0.12 dev/prod testing:

  1. Online listings empty.
     directory.go was missing Vary: Accept on its responses, so
     browser/CDN cached the HTML response (the embedded browse.html)
     and served it again when browse's JS later fetched the same URL
     with Accept: application/json. JSON parse failed, autoDetect
     returned null, empty state showed. Adds Vary: Accept on both
     branches and changes browse.html cache-control to no-cache so
     deployed updates land immediately.

  2. Top-level folder rows tall, shrink as subtree expands.
     The .browse-table had flex:1 in a flex column. <table> in flex
     doesn't reliably distribute height across rows — with few rows,
     each row stretched. Wrap the table in a div with overflow:auto
     and drop flex:1 from the table itself.

  3. Recursive expand/collapse.
     Shift-click (or alt-click) on a folder now expand-all or
     collapse-all its subtree. Plain click still toggles just that
     folder. Implementation: tree.expandSubtree() walks BFS, loading
     each level's children in parallel, re-rendering between levels
     so the user sees progress. tree.collapseSubtree() recursively
     marks the subtree collapsed (children stay loaded for instant
     re-expand).
2026-05-03 20:20:54 -05:00
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).
2026-05-03 19:56:51 -05:00