diff --git a/frontend/src/client/fristenrechner.ts b/frontend/src/client/fristenrechner.ts index bcba9ec..a85b6a4 100644 --- a/frontend/src/client/fristenrechner.ts +++ b/frontend/src/client/fristenrechner.ts @@ -2827,6 +2827,32 @@ function inboxChannelToForum(ch: InboxChannel): "upc" | "de" | null { return null; } +// inboxToFineForumSlugs maps the inbox channel to the matching subset +// of the 10-bucket B2 forum filter. CMS narrows to UPC CFI + CoA; +// beA / Posteingang narrow to all four national-DE buckets (LG / OLG / +// BGH / BPatG). Null returns an empty list — caller handles the +// "no inbox set" case explicitly because the meaning differs between +// hydrate (don't touch fine chips) and click-clear (clear them). +function inboxToFineForumSlugs(ch: InboxChannel): string[] { + if (ch === "cms") return ["upc_cfi", "upc_coa"]; + if (ch === "bea" || ch === "posteingang") return ["de_lg", "de_olg", "de_bgh", "de_bpatg"]; + return []; +} + +// applyFineForumsFromInbox replaces activeForums with the inbox's +// implied fine-bucket set and re-renders the B2 chip strip + reissues +// the active search. Caller decides when to invoke (user click always; +// hydrate / popstate only when URL ?forum= is empty so an explicit +// link-share wins over the inbox derivation). +function applyFineForumsFromInbox(ch: InboxChannel) { + activeForums.clear(); + for (const slug of inboxToFineForumSlugs(ch)) activeForums.add(slug); + // renderForumChips guards on missing container, so calling from + // contexts where Pathway B isn't yet rendered is safe. + renderForumChips(); + reissueSearchWithCurrentFilters(); +} + function applyInboxFilter(ch: InboxChannel) { const forum = inboxChannelToForum(ch); @@ -2884,18 +2910,41 @@ async function initInboxFilter() { } 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); + } + bar.querySelectorAll(".fristen-inbox-chip").forEach((btn) => { btn.addEventListener("click", () => { const isClear = btn.hasAttribute("data-inbox-clear"); const next: InboxChannel = isClear ? null : ((btn.dataset.inbox as InboxChannel) ?? null); writeInboxToURL(next); applyInboxFilter(next); + // User click is an explicit signal: re-narrow the B2 fine chips + // to match the new inbox. "Alle" clears both inbox and fine chips + // — that's the user's reset affordance. + applyFineForumsFromInbox(next); + writeForumsToURL(true); void persistInboxPref(next); }); }); window.addEventListener("popstate", () => { - applyInboxFilter(readInboxFromURL()); + 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); + } }); }