diff --git a/zddc/cmd/zddc-server/main.go b/zddc/cmd/zddc-server/main.go index d5a34f1..29407ac 100644 --- a/zddc/cmd/zddc-server/main.go +++ b/zddc/cmd/zddc-server/main.go @@ -679,7 +679,7 @@ func dispatch(cfg config.Config, idx *archive.Index, ring *handler.LogRing, apps // and addressed at exactly one depth — //.archive/... — even // though offline-built HTML files reference siblings via // "../.archive/.html" from arbitrary depths. Any deeper form - // (///.../.archive/...) gets a 301 to the project-rooted + // (///.../.archive/...) gets a 302 to the project-rooted // canonical so anchored links and bookmarks normalize to a single // stable URL per tracking number. The redirect target preserves the // path tail after .archive/ verbatim and the query string; browsers @@ -721,7 +721,7 @@ func dispatch(cfg config.Config, idx *archive.Index, ring *handler.LogRing, apps if r.URL.RawQuery != "" { target += "?" + r.URL.RawQuery } - http.Redirect(w, r, target, http.StatusMovedPermanently) + http.Redirect(w, r, target, http.StatusFound) return } handler.ServeArchive(cfg, idx, w, r, project, filename) @@ -849,14 +849,14 @@ func dispatch(cfg config.Config, idx *archive.Index, ring *handler.LogRing, apps // - HTML, with slash → browse.html (via ServeDirectory). // browse fetches JSON which routes back // through here to ServeReviewing. - // Depth-3 no-slash (reviewing/) 301s to the slash form. + // Depth-3 no-slash (reviewing/) 302s 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, "/") { if tracking != "" { - http.Redirect(w, r, urlPath+"/", http.StatusMovedPermanently) + http.Redirect(w, r, urlPath+"/", http.StatusFound) return } // Depth-2 no-slash falls through to canonical-folder block. @@ -885,7 +885,7 @@ func dispatch(cfg config.Config, idx *archive.Index, ring *handler.LogRing, apps // /working/ → ServeDirectory → fs.ListDirectory // returns 200 + [] for the empty case // - // reviewing/ has no default app, so the no-slash form 301s + // reviewing/ has no default app, so the no-slash form 302s // to the slash form. if (r.Method == http.MethodGet || r.Method == http.MethodHead) && zddc.IsProjectRootFolder(strings.Trim(strings.TrimPrefix(urlPath, "/"), "/")) { @@ -902,7 +902,7 @@ func dispatch(cfg config.Config, idx *archive.Index, ring *handler.LogRing, apps } } // No default app (reviewing/) — redirect to slash form. - http.Redirect(w, r, urlPath+"/", http.StatusMovedPermanently) + http.Redirect(w, r, urlPath+"/", http.StatusFound) return } handler.ServeDirectory(cfg, w, r) @@ -977,7 +977,7 @@ func dispatch(cfg config.Config, idx *archive.Index, ring *handler.LogRing, apps } } if !strings.HasSuffix(urlPath, "/") { - http.Redirect(w, r, urlPath+"/", http.StatusMovedPermanently) + http.Redirect(w, r, urlPath+"/", http.StatusFound) return } handler.ServeDirectory(cfg, w, r) diff --git a/zddc/cmd/zddc-server/main_test.go b/zddc/cmd/zddc-server/main_test.go index 6a2aba8..8566ea1 100644 --- a/zddc/cmd/zddc-server/main_test.go +++ b/zddc/cmd/zddc-server/main_test.go @@ -286,7 +286,7 @@ func TestDispatchRoutesWritesToFileAPI(t *testing.T) { } } -// TestDispatchArchiveRedirect: any ///.../.archive/... is 301'd +// TestDispatchArchiveRedirect: any ///.../.archive/... is 302'd // to the canonical //.archive/... so all tracking-number references // converge on a single stable URL per (project, tracking) regardless of the // folder a relative "../.archive/..." link was resolved from. @@ -318,28 +318,28 @@ func TestDispatchArchiveRedirect(t *testing.T) { "deep two segments", "/ProjectA/Working/.archive/100.html", "", - http.StatusMovedPermanently, + http.StatusFound, "/ProjectA/.archive/100.html", }, { "deep three segments", "/ProjectA/sub/sub2/.archive/100.html", "", - http.StatusMovedPermanently, + http.StatusFound, "/ProjectA/.archive/100.html", }, { "deep with trailing slash (listing)", "/ProjectA/Working/.archive/", "", - http.StatusMovedPermanently, + http.StatusFound, "/ProjectA/.archive/", }, { "deep with query string preserved", "/ProjectA/Working/.archive/100.html", "v=42", - http.StatusMovedPermanently, + http.StatusFound, "/ProjectA/.archive/100.html?v=42", }, { @@ -379,7 +379,7 @@ func TestDispatchSlashRouting(t *testing.T) { // default tool for the directory (mdedit under working/, transmittal // under staging/, archive under archive/, tables under // archive//mdl/). Without a default app, no-slash falls - // through to the legacy 301-to-trailing-slash redirect. + // through to the trailing-slash redirect (302). // // Exception: a directory that is the rows-dir of a registered table // (declared via parent .zddc tables:) — including the default-MDL @@ -439,7 +439,7 @@ func TestDispatchSlashRouting(t *testing.T) { {"archive//mdl slash → 302 in-dir table.html", "/Project/archive/Acme/mdl/", http.StatusFound, false, "/Project/archive/Acme/mdl/table.html"}, {"archive//incoming no-slash → archive", "/Project/archive/Acme/incoming", http.StatusOK, true, ""}, {"archive//incoming slash → browse", "/Project/archive/Acme/incoming/", http.StatusOK, true, ""}, - {"non-canonical no-slash → 301 to slash", "/Project/scratch", http.StatusMovedPermanently, false, ""}, + {"non-canonical no-slash → 302 to slash", "/Project/scratch", http.StatusFound, false, ""}, {"non-canonical slash → browse", "/Project/scratch/", http.StatusOK, true, ""}, // Project root no-slash → synthetic landing page (handler.ServeProjectLanding). {"project root no-slash → landing", "/Project", http.StatusOK, true, ""}, diff --git a/zddc/internal/handler/archivehandler.go b/zddc/internal/handler/archivehandler.go index 1bd5576..5d5f041 100644 --- a/zddc/internal/handler/archivehandler.go +++ b/zddc/internal/handler/archivehandler.go @@ -18,7 +18,7 @@ import ( // ServeArchive handles requests under a project's .archive virtual path. // // The dispatcher canonicalizes every .archive request to //.archive/... -// before reaching here (any deeper //sub/.../archive/... gets a 301 +// before reaching here (any deeper //sub/.../archive/... gets a 302 // to the project-rooted form), so this handler only ever sees one shape: // project = first URL segment, filename = whatever follows .archive/. // diff --git a/zddc/internal/handler/directory.go b/zddc/internal/handler/directory.go index de2d38b..5c3f4f4 100644 --- a/zddc/internal/handler/directory.go +++ b/zddc/internal/handler/directory.go @@ -42,7 +42,7 @@ func safeJoin(fsRoot, relPath string) (string, bool) { func ServeDirectory(cfg config.Config, w http.ResponseWriter, r *http.Request) { urlPath := r.URL.Path if !strings.HasSuffix(urlPath, "/") { - http.Redirect(w, r, urlPath+"/", http.StatusMovedPermanently) + http.Redirect(w, r, urlPath+"/", http.StatusFound) return } diff --git a/zddc/internal/handler/projecthandler.go b/zddc/internal/handler/projecthandler.go index 82c2768..4a1b728 100644 --- a/zddc/internal/handler/projecthandler.go +++ b/zddc/internal/handler/projecthandler.go @@ -7,7 +7,7 @@ import ( // IsProjectRootURL reports whether urlPath names a project root — // exactly one path segment, no trailing slash. Used by the dispatcher // to route / (no trailing slash) to the landing tool's -// project-workspace mode rather than the historical 301-to-slash. +// project-workspace mode rather than the historical redirect-to-slash. // // Examples: //