feat(fristenrechner/inbox-chip): wire inbox into B2 fine-forum chips

The page-top inbox chip now drives the existing 10-bucket B2 forum
filter so search results inherit the same narrowing as the Pathway A
picker. CMS auto-selects upc_cfi + upc_coa; beA / Posteingang
auto-select de_lg + de_olg + de_bgh + de_bpatg; "Alle" clears both the
inbox and the fine chips.

State-priority discipline:

  - User chip click               always replaces activeForums with
                                  the inbox-derived set.
  - Hydrate (URL ?inbox=, no
    ?forum=)                      derives activeForums from inbox.
                                  initForumFilter (which runs first)
                                  has already loaded URL ?forum= when
                                  present, so the explicit forum= wins.
  - Hydrate (no URL ?inbox=,
    /api/me forum_pref set)       same as above: derive when ?forum=
                                  is empty.
  - popstate                      re-applies the same rule so browser
                                  back/forward stays consistent.

The "explicit URL forum= wins" rule means a colleague's CMS-narrowed
share link still works when the recipient has a different saved pref —
they see the shared narrowing, not their own.

Refs m/paliad#15.
This commit is contained in:
m
2026-05-08 16:41:50 +02:00
parent 1df1bc7e40
commit e87929885d

View File

@@ -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<HTMLButtonElement>(".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);
}
});
}