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.
84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
package apps
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
_ "embed"
|
||
"sync"
|
||
)
|
||
|
||
// Embedded fallback: tool HTMLs from the time the binary was built.
|
||
// Used as a last-resort served-bytes when (cache miss) AND (upstream
|
||
// unreachable) AND (no operator override) — see handler.go.
|
||
//
|
||
// The files are populated by the top-level build.sh, which copies the
|
||
// freshly-built dist/<tool>.html into ./embedded/ before `go build` runs.
|
||
// Empty placeholder files are checked in so the package compiles when no
|
||
// build has been run yet (CI bootstrap, fresh clone, etc.); at runtime an
|
||
// empty embedded body is treated as "no embedded fallback available."
|
||
|
||
//go:embed embedded/archive.html
|
||
var embeddedArchive []byte
|
||
|
||
//go:embed embedded/transmittal.html
|
||
var embeddedTransmittal []byte
|
||
|
||
//go:embed embedded/classifier.html
|
||
var embeddedClassifier []byte
|
||
|
||
//go:embed embedded/index.html
|
||
var embeddedLanding []byte
|
||
|
||
//go:embed embedded/browse.html
|
||
var embeddedBrowse []byte
|
||
|
||
// EmbeddedBytes returns the embedded HTML for app, or nil if either app is
|
||
// not one of the canonical names or the embedded slot is empty (no build
|
||
// has populated it).
|
||
func EmbeddedBytes(app string) []byte {
|
||
var b []byte
|
||
switch app {
|
||
case "archive":
|
||
b = embeddedArchive
|
||
case "transmittal":
|
||
b = embeddedTransmittal
|
||
case "classifier":
|
||
b = embeddedClassifier
|
||
case "landing":
|
||
b = embeddedLanding
|
||
case "browse":
|
||
b = embeddedBrowse
|
||
default:
|
||
return nil
|
||
}
|
||
if len(b) == 0 {
|
||
return nil
|
||
}
|
||
return b
|
||
}
|
||
|
||
// EmbeddedETag returns a strong ETag (sha256-hex prefix, 32 chars) for the
|
||
// app's embedded bytes. Computed lazily on first call per-app and memoized
|
||
// — the embedded slot is fixed for the binary's lifetime, so the ETag
|
||
// changes only when the binary is redeployed. Empty slot returns "".
|
||
//
|
||
// Used by apps.Server.serveEmbedded to issue conditional-GET-friendly
|
||
// responses: with this ETag + Cache-Control: max-age=0, must-revalidate,
|
||
// every page load revalidates and gets a 304 unless the binary has been
|
||
// updated. Saves re-transmitting 50–920 KB tool HTMLs on every reload.
|
||
func EmbeddedETag(app string) string {
|
||
if v, ok := etagCacheByApp.Load(app); ok {
|
||
return v.(string)
|
||
}
|
||
body := EmbeddedBytes(app)
|
||
if body == nil {
|
||
return ""
|
||
}
|
||
sum := sha256.Sum256(body)
|
||
etag := hex.EncodeToString(sum[:])[:32]
|
||
etagCacheByApp.Store(app, etag)
|
||
return etag
|
||
}
|
||
|
||
// etagCacheByApp memoizes EmbeddedETag results keyed by app name.
|
||
var etagCacheByApp sync.Map
|