ZDDC/tables
ZDDC 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>
2026-05-09 19:50:30 -05:00
..
css feat(tables): editable cells phase 5 — undo + multi-cell ops 2026-05-09 10:39:26 -05:00
js fix(tables): use fetch keepalive on the beforeunload save path 2026-05-09 18:39:42 -05:00
sample feat(tables): new sortable/filterable grid tool for directories of YAML files 2026-05-05 20:32:01 -05:00
build.sh feat(shared): lateral project-stage strip in every tool's header 2026-05-09 19:50:30 -05:00
README.md feat(tables): new sortable/filterable grid tool for directories of YAML files 2026-05-05 20:32:01 -05:00
template.html feat(mdl): default columns mirror tracking-number components + customizable 2026-05-09 11:09:31 -05:00

ZDDC Tables

← Back to ZDDC

Render a directory of YAML files as a sortable, filterable table — read-only, with click-row → edit-in-form integration. Backed by zddc-server's form handler so the table view and the form editor are two sides of the same data.

Anchor use case. A Master Deliverables List (MDL) under Archive/<Party>/MDL/, where each .yaml file is one expected deliverable. Multiple parties keep their own MDLs side by side; the table aggregates within a single party's directory.

How it works

  • Storage is file-per-row YAML — one *.yaml file in a directory per table row. Concurrent edits don't collide on a shared blob, every row has independent git history, and per-row ACL inherits from the cascading .zddc chain.
  • Discovery is .zddc-declarative. Drop a tables: entry in the directory's .zddc to register the table; no file-presence auto-mount, no phantom tables from rogue YAML drops.
  • Rendering is server-side: zddc-server reads every *.yaml under the rows directory, normalizes them into a JSON list, and inlines the list into the page on render. The browser does sorting, filtering, and click-row navigation locally — no further server round-trips for those.
  • Editing is delegated to the existing form tool. Each row's click target is the form's re-edit URL (<dir>/<name>/<basename>.yaml.html), which zddc-server already serves via the form handler. The table itself never writes.

Setup (for an MDL at Archive/Acme/MDL/)

Archive/Acme/
├── .zddc                 # declares: tables: { MDL: ./MDL.table.yaml }
├── MDL.table.yaml        # column spec + rows path + row schema reference
├── MDL.form.yaml         # JSON Schema for one row (used by both the table and the form editor)
└── MDL/
    ├── D-001.yaml        # one row
    ├── D-002.yaml        # one row
    └── ...

Visit Archive/Acme/MDL.table.html and the table renders. Visit Archive/Acme/MDL.form.html to add a new row (the form handler creates a YAML in MDL/).

.zddc declaration

tables:
  MDL: ./MDL.table.yaml

The map key (MDL) becomes the URL stem and must match both the rows directory name and the form spec name. v1 enforces this with a load-time spec-validation error.

Table spec (MDL.table.yaml)

title: Master Deliverables List
description: Optional description shown above the table.
rowSchema: ./MDL.form.yaml         # path to the row's JSON Schema (form-spec format)
rows: ./MDL                        # directory of *.yaml row files (non-recursive in v1)
columns:
  - field: id                      # top-level key OR JSON Pointer (e.g. /nested/path)
    title: ID
    width: 7em
    sort: asc                      # default sort key (overridden by defaults.sort below)
  - field: title
    title: Deliverable
  - field: dueDate
    title: Due
    format: date                   # date | datetime | number | bool
  - field: status
    title: Status
    enum: [pending, submitted, accepted, rejected]   # constrains values + enables enum filter
defaults:
  sort:
    - { field: dueDate, dir: asc }
  filter:
    status: [pending, submitted]   # initial filter state; clear with the toolbar button

Columns are explicit — the renderer does not auto-derive from the row schema. Pick the subset you want to display.

ACL behavior

  • The page-level read check uses the cascade at the spec directory; a caller without r gets a 403.
  • Per-row "edit" affordance is recomputed against the row's own parent dir. If the user has w there, the row is clickable; otherwise it's plain text. Hard enforcement remains on the form-handler side (the form's POST will refuse a write the cascade denies).
  • Issued/Received archive folders are server-enforced WORM. The decider strips w/d/a from non-admin grants under those subtrees, so an MDL placed inside Issued/ shows every row as read-only with no special-casing in the table tool.

v1 limits

  • Read-only grid; click-row opens the form editor. Inline cell editing is a v2 candidate (would PUT each edit through the new file API in zddc/internal/handler/fileapi.go).
  • One directory of *.yaml per table; cross-directory aggregation (Archive/*/MDL/*.yaml as one combined view) is not yet supported.
  • No virtualization — large tables (>1000 rows) will be slow.
  • No multi-row bulk operations, no add-row UI inside the table (use the form editor at <name>.form.html).
  • .zddc tables: declarations are direct-lookup only; no upward cascade. Each directory hosting a table needs its own declaration.

Build & develop

sh tables/build.sh                       # build (writes tables/dist/tables.html)
sh tables/build.sh --release alpha       # cut alpha
sh ./build                               # full lockstep build (all tools + zddc-server)

(cd zddc && go test ./internal/handler/... ./internal/zddc/...)
npx playwright test --project=tables

Authoritative architecture and build docs are in ../AGENTS.md and ../ARCHITECTURE.md.