Commit graph

79 commits

Author SHA1 Message Date
a62960b712 chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 7s
2026-05-13 14:45:52 -05:00
9a5b293590 chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
2026-05-13 13:48:52 -05:00
1db9fd06e7 chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 7s
2026-05-13 13:10:12 -05:00
59d8ccf0fc chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
2026-05-13 13:06:55 -05:00
95c6feed16 chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
2026-05-13 12:55:21 -05:00
52a6f139bb chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
2026-05-13 12:17:59 -05:00
f37b55ddd5 chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
2026-05-13 12:07:08 -05:00
ab552c8c1b chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
2026-05-13 11:14:52 -05:00
320c5d09ab chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
2026-05-13 10:34:56 -05:00
e7f6334daa chore: retire mdedit tool — markdown editor lives in browse now
mdedit/ is gone. Its functionality moved into browse's preview plugin
(browse/js/preview-markdown.js) — YAML front matter editing, outline,
and on-demand DOCX/HTML/PDF download all happen there. Browse is the
default_tool for working/ + reviewing/ as of the previous commit, so
existing URLs of the form /<project>/working land on browse without
operator action.

Removed:

  • mdedit/ source tree (Toast UI app, CSS, JS, template, build.sh)
  • zddc/internal/apps/embedded/mdedit.html (//go:embed blob)
  • tests/mdedit.spec.js + the "mdedit" project in playwright.config.js
  • mdedit entries in zddc/internal/apps/embed.go (//go:embed, var,
    switch case in EmbeddedBytes)
  • "mdedit" in zddc/internal/zddc/validate.go AppNames + the matching
    error-message app list
  • "mdedit.html" branch in zddc/internal/apps/handler.go MatchAppHTML
  • mdedit case in tests (handler_test.go, validate_test.go,
    zddchandler_test.go) — test fixtures now use browse/classifier
  • mdedit from build (per-tool build.sh loop, tool-list literals,
    composer cards) and shared/build-lib.sh ZDDC_RELEASE_TOOLS
  • mdedit from freshen-channel's tool list and usage banner
  • mdedit-specific paragraphs in AGENTS.md and ARCHITECTURE.md;
    Markdown Editor section in ARCHITECTURE.md rewritten to point at
    browse/js/preview-markdown.js
  • mdedit from CLAUDE.md, README.md, zddc/README.md tool lists

Historical mdedit_v*.html / mdedit_v*.html.sig files in
/srv/zddc/releases/ on the deploy host are immutable history — they
stay where they are. The next ./build release cut will simply not
produce new mdedit_v* artifacts.
2026-05-13 10:34:31 -05:00
7fbe7867fd feat(zddc): defaults — browse hosts the markdown editor for working/+reviewing/
Flip default_tool from `mdedit` to `browse` (which now ships a Toast UI
markdown editor plugin in its preview pane) at:

  • paths."*".paths.working
  • paths."*".paths.working.paths."*"   (per-user homes)
  • paths."*".paths.reviewing

available_tools at those levels drops `mdedit` and adds `browse` next
to `classifier`. Operator overrides per .zddc cascade still work; only
the embedded baseline changes.

Test fixtures updated:

  • lookups_test.go     — DefaultToolAt assertions for working/+reviewing/
  • availability_test.go — AppAvailableAt + DefaultAppAt for working/+
                           reviewing/+per-user home
  • main_test.go        — dispatch route asserts "ZDDC Browse" (was "ZDDC
                          Markdown"); Apps cascade fixture swaps mdedit
                          for browse so the live route fetches the right
                          embedded HTML
2026-05-13 10:34:06 -05:00
ba7e7a3fdd chore(embedded): cut v0.0.17-beta 2026-05-12 13:25:44 -05:00
5e393cbeaf feat(zddc): Phase 3 completion — all canonical-folder behaviour now cascade-driven
Final consumer migration. The Go-coded lists that previously encoded
the ZDDC convention all defer to the .zddc cascade now.

Schema added:
  available_tools: [tool1, tool2, ...]   concat-union across cascade;
                                          tools not in the union are
                                          denied auto-route at that path
  auto_own_fenced: true|false             generated auto-own .zddc
                                          carries inherit:false (private
                                          to creator)

Lookups added:
  AvailableToolsAt(root, dir)   union of available_tools across cascade
  IsToolAvailableAt(root, dir, tool)
  AutoOwnFencedAt(root, dir)    leaf-only

Cascade semantics finalised (per field):
  default_tool      → leaf→root walk (parent applies to descendants)
  available_tools   → leaf→root union (each level adds; baseline at root)
  auto_own          → leaf-only (creating THIS dir specifically)
  auto_own_fenced   → leaf-only (same)
  virtual           → leaf-only (THIS dir is virtual, not subtree)

Consumers migrated:
  apps.DefaultAppAt        → zddc.DefaultToolAt
  apps.AppAvailableAt      → zddc.IsToolAvailableAt (+ landing special)
  EnsureCanonicalAncestors → AutoOwnAt + AutoOwnFencedAt
  fs.ListDirectory empty-list fallback     → zddc.IsDeclaredPath
  fs.virtualCanonicalFolders               → zddc.ChildrenDeclaredAt
  dispatcher canonical-folder branches     → unified into one
                                              cascade-declared block

Hardcoded helpers REMOVED (dead code):
  apps.inAncestorWithName
  zddc.autoOwnDepthMatch / isAutoOwnDepthMatch

Hardcoded lists kept as data sources for the cascade walker but
no longer drive routing logic:
  ProjectRootFolders / PartyFolders / AutoOwnCanonicalNames /
  VirtualOnlyCanonicalNames / IsProjectRootFolder / IsArchivePartyFolder /
  IsArchivePartyMdlDir — all still defined; only `ProjectRootFolders`
  is used by special.go's IsProjectRootFolder. The rest are dead.

Dispatcher unified: the previously-two branches (per-party folder vs
project-root folder) collapse into one cascade-declared-path block
that handles the slash/no-slash convention uniformly:
  - no-slash, default_tool=tables  → ServeTable (default-MDL fallback)
  - no-slash, default_tool set     → apps.Serve(tool)
  - no-slash, no default_tool      → 302 to slash form
  - slash, any                     → ServeDirectory empty-list fallback

The IsDir branch's switch also un-hardcoded — any cascade tool is
served (not just the legacy 3 names), so e.g. /Project/archive/<party>
/incoming (no slash) now serves classifier directly rather than 302'ing
to the slash form.

defaults.zddc.yaml populated with the canonical convention as the
recipe. Operators edit it (or override per-directory on disk) to
change any behaviour — no Go code changes required.

Browse drag-drop scope (working/staging/incoming) is the one remaining
client-side hardcoded regex; cascading that requires the cascade JSON
to be served to the client, which is its own Phase 4 piece.

Tests updated for the new no-slash mdl URL convention (landing MDL
card test) and no-slash stage URLs (nav strip test). All 248
Playwright + all Go tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:36:33 -05:00
9d18047a46 feat(zddc): Phase 3 — DefaultToolAt cascade propagation + apps.DefaultAppAt migration
Two pieces:

1. Lookup helpers walk chain.Levels from leaf back to root. The
   "parent applies to descendants unless overridden" cascade rule
   means a working/ default_tool=mdedit propagates to deep paths
   like working/alice@example.com/notes/sub/deep without anyone
   declaring it at every level. AutoOwnAt and VirtualAt follow the
   same walk; explicit false at a descendant can override an
   ancestor's true (*bool semantics).

2. apps.DefaultAppAt delegates to zddc.DefaultToolAt. The hardcoded
   switch on parts[1] (archive→archive, staging→transmittal,
   working→mdedit, reviewing→mdedit, mdl→tables) and its case-
   sensitivity quirks now live in defaults.zddc.yaml. Operators can
   override any of these per-directory with an on-disk .zddc; no
   code change required.

Semantic improvement: archive/<party>/incoming previously defaulted
to "archive" (because parts[1]=archive and the switch didn't look
deeper). The new convention routes it to "classifier" — incoming/ is
the bulk-rename surface, not a record browser. Updated
availability_test.go to reflect.

All other DefaultAppAt cases — including case-fold (Archive/MDL),
mdl override, reviewing virtual, project root returning "", random
non-canonical names returning "" — produce bit-identical output.

Two new tests in lookups_test.go cover the propagation:
  - TestDefaultToolAt_PropagatesToDescendants
  - TestAutoOwnAt_DescendantCanDisable

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:05:36 -05:00
c87fb7f4fa chore(embedded): cut v0.0.17-beta 2026-05-11 11:51:58 -05:00
f2af379ff5 chore(embedded): cut v0.0.17-beta 2026-05-10 19:22:14 -05:00
89d96b784f chore(embedded): cut v0.0.17-beta 2026-05-10 19:02:43 -05:00
d77981407c chore(embedded): cut v0.0.17-beta 2026-05-10 15:47:02 -05:00
875870501e chore(embedded): cut v0.0.17-beta 2026-05-10 15:09:49 -05:00
7ac2e1cc73 chore(embedded): cut v0.0.17-beta 2026-05-10 14:37:13 -05:00
9a98901683 chore(embedded): cut v0.0.17-beta 2026-05-10 14:23:00 -05:00
4acf348b21 chore(embedded): cut v0.0.17-beta 2026-05-10 14:21:18 -05:00
baf5958174 chore(embedded): cut v0.0.17-beta 2026-05-10 14:16:34 -05:00
b3e8c4127f chore(embedded): cut v0.0.17-beta 2026-05-10 14:09:33 -05:00
4cd39998aa chore(embedded): cut v0.0.17-beta 2026-05-10 07:57:40 -05:00
bc5fcf6c73 chore(embedded): cut v0.0.17-beta 2026-05-10 07:47:10 -05:00
cf5d7c2ea6 chore(embedded): cut v0.0.17-beta 2026-05-10 07:34:38 -05:00
837cf47924 chore(embedded): cut v0.0.17-beta 2026-05-10 07:26:31 -05:00
e51d9fe908 chore(embedded): cut v0.0.17-beta 2026-05-10 06:44:45 -05:00
94323ea356 chore(embedded): cut v0.0.17-beta 2026-05-09 22:34:24 -05:00
464c80c1e1 chore(embedded): cut v0.0.17-beta 2026-05-09 21:51:08 -05:00
13e929b029 chore(embedded): cut v0.0.17-beta 2026-05-09 21:39:04 -05:00
002e034119 chore(embedded): cut v0.0.17-beta 2026-05-09 21:37:22 -05:00
45005d164e feat(zddc-server): reviewing/ virtual aggregator + mdedit at the URL
Implements the reviewing/ aggregator described in the saved
project memory (~/.claude/projects/-home-user-src-zddc/memory/
project_reviewing_folder_design.md). reviewing/ stays in
VirtualOnlyCanonicalNames — never materialised on disk — and is
served as a join over archive/<party>/received/, archive/<party>/
issued/, and staging/, recomputed on every read.

Two depths, both trailing-slash:

  GET <project>/reviewing/?json=1
    → array of virtual <tracking>/ entries, one per submittal in
      archive/<party>/received/ that doesn't yet have a matching
      archive/<party>/issued/ entry. Sorted by tracking. URLs stay
      under reviewing/ so the user can drill into the per-submittal
      view. ACL: per-party, filtered like fs.ListDirectory.

  GET <project>/reviewing/<tracking>/?json=1
    → array of two virtual entries, received/ + staged/, with
      canonical URLs pointing back to archive/<party>/received/...
      and staging/... respectively. staged/ is omitted when no
      response draft exists yet.

When the response moves staging/ → archive/<party>/issued/, the
entry vanishes from depth-0 on the next listing. No mutation of
the reviewing/ subtree itself; pure join, recomputed on read.

Front-end at <project>/reviewing[/<tracking>/] is mdedit (per
user request). DefaultAppAt + AppAvailableAt extended to recognise
"reviewing" as a canonical mdedit-bearing folder. The polyfill in
shared/zddc-source.js is updated to follow listing entries' explicit
url field when present (absolute or root-relative) — that's how
mdedit's tree follows the depth-1 received/ + staged/ links into
the canonical archive/staging subtrees.

Dispatcher routing in zddc-server/main.go:
  - GET <project>/reviewing/[<tracking>/] with Accept: json
    → ServeReviewing
  - GET <project>/reviewing/[<tracking>/] with Accept: html
    → mdedit (rooted at the virtual path; polyfill fetches the
      JSON listing on its own)
  - GET <project>/reviewing (no slash) → mdedit (via DefaultAppAt)
  - GET <project>/reviewing/<tracking> (no slash) → 301 to slash form

Tests:
  - handler/reviewinghandler_test.go (6 cases): IsReviewingPath
    classification + ServeReviewing depth-0/depth-1 with and without
    staged drafts + 404 on unknown tracking + empty when archive/ is
    absent.
  - apps/availability_test.go updated: reviewing/ now expects mdedit
    rather than "" (no default).
  - cmd/zddc-server/main_test.go: TestDispatchEmptyCanonicalProjectFolders
    extended to assert reviewing → mdedit at the no-slash form;
    older "no-slash/reviewing → 301" test removed.

Future work (not in this commit): write translation. Editing a file
under reviewing/<tracking>/staged/<f>.md works today because the
polyfill rewrites to /<project>/staging/<response>/<f>.md before
fetching — the user's URL bar moves to the canonical path on click.
A virtual-filesystem mode where the URL bar stays under reviewing/
throughout would require server-side write rewriting (translate
PUT/DELETE on reviewing/.../staged/... into the canonical staging/
path). Not needed for the MVP — links in mdedit's tree work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:37:08 -05:00
d08dcce211 chore(embedded): cut v0.0.17-beta 2026-05-09 21:13:24 -05:00
03babd34d2 chore(embedded): cut v0.0.17-beta 2026-05-09 20:59:43 -05:00
1d12cfe804 chore(embedded): cut v0.0.17-beta 2026-05-09 20:35:10 -05:00
702ccf3be0 chore(embedded): cut v0.0.17-beta 2026-05-09 20:27:35 -05:00
585e84f2f4 chore(embedded): cut v0.0.17-beta
Beta cut of the eight HTML tools into zddc/internal/apps/embedded/*
and the unified form/tables bundle into zddc/internal/handler/tables.html.
Each tool's on-page label changes from alpha → beta-stamped bytes;
no source changes beyond the build label itself.

The dev image (Dockerfile, devshell, ZDDC_REF=main) and the bitnest
test container both pick this up automatically — bitnest's path-unit
fired on the rebuild of zddc/dist/zddc-server-linux-amd64 and
restarted the container with the new embedded apps:

  embedded_apps=archive=v0.0.17-beta browse=v0.0.17-beta
                classifier=v0.0.17-beta form=v0.0.17-beta
                landing=v0.0.17-beta mdedit=v0.0.17-beta
                tables=v0.0.17-beta transmittal=v0.0.17-beta

Source-side commits since the previous beta:
  feat(landing): single-project click → <project>/archive.html
  feat(shared): non-blocking toast helper
  feat(shared): lateral project-stage strip
  feat(form): standalone empty-state welcome
  fix(tables): keepalive on beforeunload save path
  refactor(mdedit): drop window.* TOC globals
  refactor(archive): remove dead debounce
  style(transmittal): tokenize utility classes, drop !important block
  style: replace inline styles with CSS
  test(shared): zddc-source.js + toast + nav specs
  test(browse): smoke spec
  docs: tool counts + state pattern + polyfill gaps

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:13:21 -05:00
e6d9966593 refactor(tables): in-dir convention + unified table+form HTML bundle
Two intertwined refactors that share too many files to split cleanly.
Both are described separately below.

PART 1 — in-dir convention for table+form spec files

Old layout had the spec at the parent and rows in a child:

    archive/<party>/
      mdl.table.yaml         spec
      mdl.form.yaml          row-edit form
      mdl/                   rows-dir
        row-001.yaml ...

URLs were /<dir>/mdl.table.html and /<dir>/mdl.form.html. Copying
mdl/ elsewhere lost the spec and form because they lived next door.

New layout collapses everything into the rows-dir:

    archive/<party>/mdl/      self-contained
      table.yaml              spec
      form.yaml               row-edit form
      row-001.yaml ...        rows

URLs become /<dir>/mdl/table.html and /<dir>/mdl/form.html. The
"copying-the-folder-takes-everything" property the user asked for
falls out by construction; the row-edit URL /<dir>/<id>.yaml.html
keeps the same shape (spec is now in the same dir, not the
grandparent).

Server changes:

- internal/handler/tablehandler.go RecognizeTableRequest fires on
  /<dir>/table.html when <dir>/table.yaml exists. The .zddc.tables
  alias map is gone — pure presence-based discovery now matches
  the form system's existing convention. Default-MDL fallback at
  archive/<party>/mdl/ stays for the virgin-archive case (the
  rows-dir need not exist on disk; the URL renders fully virtually).

- internal/handler/formhandler.go RecognizeFormRequest fires on
  /<dir>/form.html and /<dir>/<id>.yaml.html with spec at
  <dir>/form.yaml. specEligible accepts on-disk files OR the
  default-MDL virtual path so an empty mdl/ dir still surfaces the
  add-row form.

- internal/handler/tablehandler.go IsDefaultMdlSpec moves to
  serving archive/<party>/mdl/{table,form}.yaml (5 segments after
  ZDDC_ROOT). New isAtArchivePartyMdlLevel predicate; new
  isAtArchivePartyMdlDir for directory-based recognition. New
  IsDefaultMdlSpecAbs accessor for callers that hold an abs path
  rather than a URL (formhandler).

- internal/handler/formhandler.go loadFormSpec(fsRoot, path) falls
  back to embedded default-MDL bytes when os.ReadFile returns
  NotExist AND the path matches the archive-party-mdl shape. Three
  call sites updated to pass cfg.Root.

- internal/handler/formhandler.go serveFormCreate writes
  submissions to filepath.Dir(req.SpecPath) — the spec, the form,
  and rows all live in one directory. The submissionsDir creation
  is idempotent (MkdirAll); cascade falls back one level for ACL
  evaluation when the dir hasn't been materialized yet.

- internal/handler/tablehandler.go tableRowsRedirect now points at
  /<dir>/table.html (was /<dir>.table.html) when the directory
  request maps to a recognized table.

- cmd/zddc-server/main.go dispatch synth flips from
  urlPath + ".table.html" to urlPath + "/table.html" for the
  no-trailing-slash → tables-app routing.

- internal/apps/availability.go DefaultAppAt comment clarified
  that the dir at archive/<party>/mdl/ IS the table (not a child).

Client changes:

- tables/js/context.js walkServer fetches <currentdir>/table.yaml
  directly — no .zddc walk for table declarations. Rows are every
  *.yaml in current dir EXCLUDING table.yaml and form.yaml. The
  .zddc fetch-for-aliases is gated on file:// (online mode 404s
  on .zddc reads via the dispatcher's reserve guard, so skipping
  the request avoids browser console noise).

- tables/js/main.js add-row button links to relative form.html
  (same dir).

- tables/js/render.js + filters.js: every column's autofilter is
  uniformly a text-contains input, even enum columns — keeps the
  filter row visually consistent and doesn't constrain users to
  the enum vocabulary.

PART 2 — unified table+form HTML bundle

The form-render and table-render code paths share field schemas,
the cell editor for excel-mode IS a form widget, and the form
system's POST-back / validation already exists. Combining the two
HTMLs eliminates duplicating jsyaml/jsonschema/theme/source-
detection/.zddc-parsing across two single-file tools.

- tables/template.html grows two top-level mode containers:
  #table-mode (toolbar + sortable table) and #form-mode (form +
  submit button). Both hidden at parse time; the dispatcher
  unhides one. The shared #form-context placeholder was added
  here so the server's existing injectFormContext target
  resolves.

- tables/js/mode.js (new) sets window.zddcMode synchronously
  based on URL pattern: /form.html or /<id>.yaml.html → form,
  /table.html → table, else inline-context fallback for
  file:// (whichever context blob is non-empty wins). Unhides
  the matching container at DOMContentLoaded.

- tables/js/main.js init() and form/js/main.js boot() each guard
  early when mode isn't theirs. Both apps live on different
  globals (window.tablesApp vs window.formApp) so module
  registration doesn't collide.

- form/js/main.js title write falls back from #form-title to
  #table-title (the unified bundle's shared header element)
  when the dedicated id isn't present.

- tables/build.sh concatenates form modules (widgets, render,
  object, array, errors, post, serialize, util) and form CSS.
  No new external deps. Bundle grows from ~95KB to ~120KB.

- internal/handler/formhandler.go drops the //go:embed form.html
  directive; serveFormRender now writes embeddedTablesHTML via
  a small formRenderHTML() accessor (var declared in
  tablehandler.go, same package). The embedded form.html file
  is removed.

- build script: cp form/dist/form.html → internal/handler/form.html
  step is gone (file no longer exists in the source tree). cp
  tables/dist/tables.html → internal/handler/tables.html now
  runs unconditionally rather than only on beta/stable cuts —
  the renderer is a fixed binary component and dev iteration
  needs the embedded copy refreshed every build. Channel-cascaded
  apps (internal/apps/embedded/) stay channel-gated as before.

- form/dist/form.html still builds for standalone offline-only
  use (downloadable from /releases/), but no longer goes into
  the binary.

Tests:

- internal/handler/tablehandler_test.go and formhandler_test.go
  rewritten for the in-dir layout. New test
  TestRecognizeFormRequest_DefaultMdlAtArchiveParty covers
  empty-form, create POST, re-edit row, and the negative cases
  (Working/, non-mdl name) where the fallback must NOT fire.

- internal/handler/directory_test.go updated for the new
  /<dir>/table.html redirect target.

- cmd/zddc-server/main_test.go TestDispatchSlashRouting Location
  expectation updated.

- tests/form-safety.spec.js loads tables/dist/tables.html
  (named form.html in the temp dir to trigger form-mode in the
  dispatcher) so it tests the same bytes the server returns.
  Title-element selector switches to #table-title.

- tests/tables.spec.js updates the status-filter test for the
  uniform text-input filter.

Docs:

- AGENTS.md form-data system rewrites the URL conventions and
  storage layout for in-dir; gains a Tables system section
  parallel to forms describing the self-contained-directory
  property; subfolder rules ("one table per folder by
  construction; subfolders allowed and silently ignored as rows
  — legitimate uses: nested sub-tables, per-row attachments,
  drafts, future history sidecars") so we don't re-derive this.

Not included (deferred):

- ACL gating on cell-level writes — not relevant until Phase 3.
- Editable cells UI — separate commit (Phase 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:15:26 -05:00
dd889b4801 chore(embedded): cut v0.0.17-beta 2026-05-08 10:31:34 -05:00
ba20e3e5ba chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 5s
2026-05-07 12:13:04 -05:00
ecd48e5f74 chore(embedded): cut v0.0.17-beta 2026-05-07 12:11:53 -05:00
7c72ca3b1d chore(embedded): cut v0.0.17-beta 2026-05-07 12:08:23 -05:00
f7958d7b22 feat(dispatch): trailing slash → browse, no slash → canonical default tool
URL convention for directories under a project:

- <dir>/  (with trailing slash)  → browse (the directory view; same
                                     behaviour as today)
- <dir>   (without trailing slash) → the canonical default tool for
                                     that directory's context, served
                                     inline (no 301 hop)

Tool mapping via the new apps.DefaultAppAt(root, dir):

  - working/...               → mdedit
  - staging/...               → transmittal
  - archive/                  → archive
  - archive/<party>/          → archive
  - archive/<party>/incoming|received|issued/...  → archive
  - archive/<party>/mdl/...   → tables (the per-party MDL grid editor)

Directories outside the canonical layout (project root, scratch
folders) keep the legacy 301-to-trailing-slash redirect since no
default tool fits.

This generalises and replaces the bespoke
"GET archive/<party>/mdl/ → 302 mdl.table.html" redirect added in PR4.
The new dispatcher rule serves the table app inline at the bare-mdl
URL by routing through RecognizeTableRequest with the canonical
.table.html suffix appended; relative fetches resolve identically
because both URLs share the same parent directory.

Tests: TestDefaultAppAt covers all canonical positions plus
case-fold and out-of-tree edges. TestDispatchSlashRouting (replacing
the now-obsolete TestDispatchMdlRedirect) verifies the slash-vs-no-
slash distinction at every canonical folder + non-canonical
fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:26:32 -05:00
9f97bfab3e feat(zddc)!: per-party WORM + auto-own; case-fold tool availability
BREAKING CHANGE. Project-level Issued/Received/Incoming folders no
longer carry special semantics. WORM enforcement and auto-ownership
move to the per-party canonical layout:

  - WORM mask now triggers on archive/<party>/received/ and
    archive/<party>/issued/ (any case, any party)
  - Auto-own .zddc writes on first mkdir under working/, staging/,
    or archive/<party>/incoming/ (any case)

Predicate API:
  - IsAutoOwnPath(parentDir, fsRoot)  — replaces IsAutoOwnParent(name)
  - IsWormPath(requestPath)           — same name, new pattern
  - WormFolderLevelIndex unchanged signature, new pattern

Legacy SpecialFolderNames / AutoOwnFolderNames / WormFolderNames /
IsAutoOwnParent are deleted (no Deprecated: stubs — early-development
project, no back-compat to preserve).

Tool availability (apps/availability.go) is case-fold throughout:
  - mdedit:     descendants of working/
  - transmittal: descendants of staging/
  - classifier: descendants of working/, staging/, or
                archive/<party>/incoming/
Working/, WORKING/, working/ all match identically.

Test fixtures rewritten:
  - special_test.go: covers IsAutoOwnPath / IsWormPath /
    WormFolderLevelIndex / ResolveCanonical / canonical lists
  - availability_test.go: per-party rules, case-fold scenarios
  - fileapi_test.go: rolePermissionsTestSetup now seeds
    Project-X/archive/Acme/{incoming,issued,received}/ rather than
    Vendor/{Incoming,Issued,Received}/ at the project root

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 09:14:19 -05:00
f6dc9d557a chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
2026-05-07 06:28:22 -05:00
8cf1aa1002 release: v0.0.16 lockstep
Some checks failed
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
Build + deploy releases / build-and-deploy (push) Failing after 9s
Build + deploy releases / notify-chart-prod (push) Has been skipped
2026-05-06 09:41:01 -05:00
748cc59ce3 chore(embedded): cut v0.0.16-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 5s
2026-05-05 20:48:22 -05:00
4e6473a5cd chore(embedded): cut v0.0.16-beta with tables tool
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 5s
Refreshes //go:embed bytes off fe28a73. Dev image now ships the new
tables renderer (handler-local embed at handler/tables.html) plus
build-label refresh on the six cascade-served tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 20:35:38 -05:00