feat(zddc-server): reviewing/ slash form serves browse, no-slash serves mdedit
Apply the same slash-vs-no-slash convention to reviewing/ that already
governs the other three canonical project folders:
/<project>/reviewing → mdedit (default tool, via DefaultAppAt)
/<project>/reviewing/ → browse (HTML) — shows the aggregator's
virtual <tracking>/ entries as a tree
/<project>/reviewing/?json → aggregator JSON (handler.ServeReviewing)
Browse fetches the JSON listing for the URL it was loaded from, so
loading browse.html at /<project>/reviewing/ triggers a JSON request
back through the dispatcher → ServeReviewing → aggregator output.
Browse then renders the virtual <tracking>/ entries as clickable
folders. Clicking a tracking folder navigates to the per-submittal
view; clicking received/ or staged/ exits the virtual subtree
into canonical archive/ or staging/ paths via the polyfill's
explicit-url support.
The HTML branch in the reviewing dispatcher block was previously
calling appsSrv.Serve(..., "mdedit", ...) for trailing-slash URLs;
now it falls through to the canonical-folder block which routes to
ServeDirectory's HTML default (embedded browse.html).
Test: TestDispatchEmptyCanonicalProjectFolders extended with the
slash/<stage> → browse subtests, mirroring the no-slash → default
app set. All four canonical folders now have symmetric coverage of
both shapes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
94323ea356
commit
2f93fc1854
2 changed files with 34 additions and 18 deletions
|
|
@ -842,15 +842,16 @@ func dispatch(cfg config.Config, idx *archive.Index, ring *handler.LogRing, apps
|
|||
}
|
||||
}
|
||||
// Reviewing aggregator. <project>/reviewing/[<tracking>/] is
|
||||
// a virtual view. With trailing slash:
|
||||
// - JSON request → aggregator listing (handler.ServeReviewing)
|
||||
// - HTML request → mdedit, rooted at the reviewing/ path.
|
||||
// mdedit's polyfill then fetches the JSON
|
||||
// listing on its own.
|
||||
// Without trailing slash, depth-3 (reviewing/<tracking>) 301s
|
||||
// to the slash form; depth-2 (reviewing) falls through to the
|
||||
// canonical-folder block below where DefaultAppAt routes to
|
||||
// mdedit and the no-slash branch serves it directly.
|
||||
// a virtual view. The shape rule mirrors the other canonical
|
||||
// folders (slash → browse, no-slash → default tool):
|
||||
// - JSON request, any depth → aggregator listing (handler.ServeReviewing)
|
||||
// - HTML, no slash → mdedit (default tool, via DefaultAppAt)
|
||||
// - HTML, with slash → browse.html (via ServeDirectory).
|
||||
// browse fetches JSON which routes back
|
||||
// through here to ServeReviewing.
|
||||
// Depth-3 no-slash (reviewing/<tracking>) 301s to the slash form.
|
||||
// Depth-2 no-slash (reviewing) falls through to the canonical-
|
||||
// folder block below where DefaultAppAt routes to mdedit.
|
||||
if r.Method == http.MethodGet || r.Method == http.MethodHead {
|
||||
if proj, tracking, ok := handler.IsReviewingPath(urlPath); ok {
|
||||
if !strings.HasSuffix(urlPath, "/") {
|
||||
|
|
@ -859,21 +860,17 @@ func dispatch(cfg config.Config, idx *archive.Index, ring *handler.LogRing, apps
|
|||
return
|
||||
}
|
||||
// Depth-2 no-slash falls through to canonical-folder block.
|
||||
} else {
|
||||
} else if strings.Contains(r.Header.Get("Accept"), "application/json") {
|
||||
chain, _ := zddc.EffectivePolicy(cfg.Root, filepath.Join(cfg.Root, proj))
|
||||
if allowed, _ := policy.AllowFromChain(r.Context(), handler.DeciderFromContext(r), chain, email, urlPath); !allowed {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if strings.Contains(r.Header.Get("Accept"), "application/json") {
|
||||
handler.ServeReviewing(cfg, w, r, proj, tracking)
|
||||
return
|
||||
}
|
||||
if appsSrv != nil {
|
||||
appsSrv.Serve(w, r, "mdedit", chain, absPath)
|
||||
return
|
||||
}
|
||||
handler.ServeReviewing(cfg, w, r, proj, tracking)
|
||||
return
|
||||
}
|
||||
// HTML trailing-slash falls through to canonical-folder
|
||||
// block → ServeDirectory → embedded browse.html.
|
||||
}
|
||||
}
|
||||
// Canonical project-root folder fallback. <project>/{archive,
|
||||
|
|
|
|||
|
|
@ -548,6 +548,25 @@ func TestDispatchEmptyCanonicalProjectFolders(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// Trailing-slash form on a canonical folder serves the browse
|
||||
// app for HTML requests — same convention as the existing IsDir
|
||||
// branch. The slash-vs-no-slash distinction is the user's signal:
|
||||
// "show me the directory contents" vs "open the default tool".
|
||||
for _, stage := range []string{"working", "staging", "archive", "reviewing"} {
|
||||
t.Run("slash/"+stage+" → browse", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/Project/"+stage+"/", nil)
|
||||
req.Header.Set("Accept", "text/html")
|
||||
rec := httptest.NewRecorder()
|
||||
dispatch(cfg, idx, ring, appsSrv, nil, rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d, want 200; body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), "ZDDC Browse") {
|
||||
t.Errorf("%s/ HTML response missing 'ZDDC Browse'", stage)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Non-canonical missing folder still 404s (the fallback is
|
||||
// scoped to the four canonical names, not a blanket "missing →
|
||||
// empty" rule).
|
||||
|
|
|
|||
Loading…
Reference in a new issue