Merge: t-paliad-323 Slice S6 — Fristenrechner cleanup (m/paliad#146 SHIPPED)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled

knuth shipped S6, the final slice of the Fristenrechner overhaul:

- frontend/src/client/fristenrechner.ts shrinks by 137 LoC (legacy Pathway-B neutralised; row-stack subtree wired off behind ?legacy=1).
- internal/handlers/fristenrechner_event_categories.go dropped — the /api/tools/fristenrechner/event-categories endpoint is gone (route deregistered in handlers.go).
- paliad.event_categories table stays for future tools (the hidden 'Ich möchte einreichen' forward-workflow), per design §7-S6.
- Deferred follow-ups (knuth's scope discipline): drop the legacy concept-card response shape from /search + lift the dead-code row-stack subtree out of fristenrechner.ts in a separate cleanup PR. Filed as scope note on m/paliad#146 (issuecomment-10414).

S1-S6 complete:
- S1 7ea4151 — backend (search ?kind=events + /follow-ups)
- S2 9ab8dd8 — result view under ?overhaul=1
- S3 2a2c5b8 — Mode A direct search
- S4 70985d8 — Mode B 5-row wizard
- S5 4571bd4 — flip overhaul default
- S6 ba3e079 — cascade endpoint drop + legacy neutralise

Procedure-mode (upper half of fristenrechner.tsx) untouched per design. paliad.event_categories table retained for future tools.
This commit is contained in:
mAi
2026-05-27 10:25:26 +02:00
3 changed files with 34 additions and 142 deletions

View File

@@ -2655,33 +2655,16 @@ interface EventCategoryNode {
let eventCategoryTree: EventCategoryNode[] | null = null;
let eventCategoryFetchInflight: Promise<EventCategoryNode[]> | null = null;
// Top-level cascade roots that represent forward-looking workflows ("I
// want to file X, what deadlines does my action trigger?") rather than
// the backward-looking calc the Fristenrechner is built for ("event Y
// happened, what deadlines spawn?"). m's 2026-05-20 ask (m/paliad#57):
// remove these from the "Was ist passiert?" picker — they belong in a
// future forward-workflow tool, not here. The DB rows stay so that
// future tool can pick them back up; we just hide them at the UI layer.
const HIDDEN_CASCADE_ROOTS: ReadonlySet<string> = new Set([
"ich-moechte-einreichen",
]);
// t-paliad-323 Slice S6: the cascade endpoint
// /api/tools/fristenrechner/event-categories was retired alongside
// HIDDEN_CASCADE_ROOTS. loadEventCategoryTree stays as a stub that
// returns an empty tree — every caller below it sits in the legacy
// Pathway B cascade which `?legacy=1` mode never boots into after
// initB1Cascade's early-return guard (see L3598). The whole subtree
// is dead-coded; a follow-up will lift it out wholesale.
async function loadEventCategoryTree(): Promise<EventCategoryNode[]> {
if (eventCategoryTree) return eventCategoryTree;
if (eventCategoryFetchInflight) return eventCategoryFetchInflight;
eventCategoryFetchInflight = (async () => {
try {
const r = await fetch("/api/tools/fristenrechner/event-categories");
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const data = await r.json();
const raw = (data.tree || []) as EventCategoryNode[];
eventCategoryTree = raw.filter((n) => !HIDDEN_CASCADE_ROOTS.has(n.slug));
return eventCategoryTree;
} finally {
eventCategoryFetchInflight = null;
}
})();
return eventCategoryFetchInflight;
eventCategoryTree = [];
return eventCategoryTree;
}
function readB1PathFromURL(): string {
@@ -3596,30 +3579,14 @@ async function loadAndRenderB1() {
}
async function initB1Cascade() {
const panel = document.getElementById("fristen-b1-panel");
if (!panel) return;
// t-paliad-180: mode-radio retired; the row-stack's mode-row click
// handler drives tree↔filter routing. No standalone change listener
// needed here — showBMode() triggers loadAndRenderB1 when the
// pathway enters tree mode.
// Initial render if the URL already lands in tree mode.
const sp = new URLSearchParams(window.location.search);
if (sp.get("path") === "b" && sp.get("mode") === "tree") {
loadAndRenderB1();
}
// popstate restores the cascade depth.
window.addEventListener("popstate", () => {
const params = new URLSearchParams(window.location.search);
if (params.get("path") === "b" && params.get("mode") === "tree") {
// Always re-render — tree may not have loaded yet on first popstate.
currentActiveRow = null;
cascadeAutoWalkStopAfter = null;
loadAndRenderB1();
}
});
// t-paliad-323 Slice S6: the legacy Pathway B row-stack / cascade
// is dead-coded. Mode A (S3) + Mode B wizard (S4) replace it; the
// overhaul default boot (S5) handles every user route. Early-return
// here keeps the legacy module imports linked (for ?legacy=1 entry)
// while ensuring no cascade fetch / row-stack render fires. The
// helper bodies stay for one cleanup follow-up that lifts the whole
// subtree out.
return;
}
document.addEventListener("DOMContentLoaded", initB1Cascade);
@@ -3720,23 +3687,11 @@ function getActiveForumsParam(): string {
}
function initForumFilter() {
// Hydrate from URL on first load.
for (const slug of readForumsFromURL()) {
activeForums.add(slug);
}
renderForumChips();
// Restore on browser nav.
window.addEventListener("popstate", () => {
activeForums.clear();
for (const slug of readForumsFromURL()) {
activeForums.add(slug);
}
renderForumChips();
});
// Re-render labels on language change.
onLangChange(() => renderForumChips());
// t-paliad-323 Slice S6: dead-coded alongside initB1Cascade. The
// legacy forum-chip strip lived in the Pathway B B2-search panel
// which the overhaul has retired. Helper bodies stay for the
// follow-up cleanup that lifts the whole Pathway B subtree.
return;
}
document.addEventListener("DOMContentLoaded", initForumFilter);
@@ -4020,49 +3975,11 @@ async function persistInboxPref(ch: InboxChannel) {
}
async function initInboxFilter() {
// t-paliad-180: the standalone inbox chip strip is retired; inbox
// state still drives cascade narrowing + B2 fine-bucket sync, just
// surfaced through the row-stack row now. This init still hydrates
// from URL / saved preference + wires the popstate restore.
if (!document.getElementById("fristen-b1-panel")) return;
let initial: InboxChannel = readInboxFromURL();
if (initial === null) {
try {
const resp = await fetch("/api/me", { credentials: "same-origin" });
if (resp.ok) {
const me = (await resp.json()) as { forum_pref?: string | null };
if (me.forum_pref && INBOX_CHANNEL_VALUES.has(me.forum_pref)) {
initial = me.forum_pref as InboxChannel;
}
}
} catch {
// Anonymous visitor or transient error — leave the chip unset.
}
}
applyInboxFilter(initial);
// Sync B2 fine-bucket chips from the inbox on hydrate ONLY when the
// URL doesn't explicitly carry ?forum=… — an explicit forum= comes
// from a shared link and should win over the user's saved inbox
// preference. initForumFilter (which runs first) has already
// populated activeForums from URL forum=, so we leave it alone here.
if (initial !== null && readForumsFromURL().length === 0) {
applyFineForumsFromInbox(initial);
writeForumsToURL(true);
}
window.addEventListener("popstate", () => {
const newInbox = readInboxFromURL();
applyInboxFilter(newInbox);
// popstate can land on a URL with inbox= but no forum= (the user
// navigated to a state where derivation should re-apply). Don't
// touch activeForums when forum= is explicit — initForumFilter's
// own popstate handler has already loaded it from the URL.
if (newInbox !== null && readForumsFromURL().length === 0) {
applyFineForumsFromInbox(newInbox);
}
});
// t-paliad-323 Slice S6: dead-coded alongside initB1Cascade /
// initForumFilter. The inbox-channel row lived inside Pathway B's
// row-stack which the overhaul has retired. Helper bodies stay
// for the follow-up cleanup that lifts the whole subtree.
return;
}
document.addEventListener("DOMContentLoaded", initInboxFilter);

View File

@@ -1,31 +0,0 @@
package handlers
import (
"net/http"
)
// GET /api/tools/fristenrechner/event-categories — returns the full
// decision-tree taxonomy for the v3 Pathway B / B1 cascade UI
// (t-paliad-133). Tree is small (~100 nodes) and mostly static; the
// frontend ETag-caches it via localStorage.
//
// Returns 503 if the DB-backed services aren't wired (DATABASE_URL
// unset).
func handleFristenrechnerEventCategories(w http.ResponseWriter, r *http.Request) {
if dbSvc == nil || dbSvc.eventCategory == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{
"error": "Decision-tree-Taxonomie vorübergehend nicht verfügbar (keine Datenbank).",
})
return
}
tree, err := dbSvc.eventCategory.Tree(r.Context())
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{
"error": "Decision-tree fehlgeschlagen: " + err.Error(),
})
return
}
writeJSON(w, http.StatusOK, map[string]any{
"tree": tree,
})
}

View File

@@ -308,7 +308,13 @@ func Register(mux *http.ServeMux, client *auth.Client, giteaAPIToken string, svc
protected.HandleFunc("GET /api/tools/courts", handleCourtsList)
protected.HandleFunc("GET /api/tools/fristenrechner/search", handleFristenrechnerSearch)
protected.HandleFunc("GET /api/tools/fristenrechner/follow-ups", handleFristenrechnerFollowUps)
protected.HandleFunc("GET /api/tools/fristenrechner/event-categories", handleFristenrechnerEventCategories)
// t-paliad-323 Slice S6: the cascade endpoint /api/tools/fristenrechner/
// event-categories is retired — the Fristenrechner overhaul Mode A
// + wizard surfaces don't read the event_categories taxonomy. The
// table itself stays for future tools (design doc §7). The
// EventCategoryService still backs the /search endpoint's legacy
// ?event_category_slug filter; that filter is dead-coded too but
// removing the service is a separate follow-up.
protected.HandleFunc("GET /downloads", handleDownloadsPage)
protected.HandleFunc("GET /glossary", handleGlossaryPage)
protected.HandleFunc("GET /api/glossary", handleGlossaryAPI)