route-inbox.ts5.1 KBView on GitHub import {
DEFAULT_INBOX,
findInboxBySlug,
slugifyInboxName,
type InboxConfig,
} from '@/modules/threads/hooks/use-inboxes';
import {
getSystemFolderSplit,
type SystemFolderSplit,
} from '@/modules/threads/lib/system-folder-splits';
/**
* Which inbox a `/mail/:folder` route is on — resolved from the URL, for every surface.
*
* The email list and the unified feed used to answer this differently: `useThreads` fell back
* to the PERSISTED `settings.activeInboxId` while the feed read the slug, so on `/mail/inbox`
* with any other tab active the two asked different questions before the server was reached —
* one list showing Important under a tab claiming to be the whole Inbox. The URL wins: it is
* what the user navigated to, it is what is shareable, and `activeInboxId` is a preference
* that trails it (the folder route syncs the setting FROM the slug, never the reverse).
*/
export type RouteInboxResolution = {
/**
* The inbox this route means, or `undefined` when the route is not an inbox route at all
* (a Gmail label folder, a search). Callers gate on their own "is this a compiled-query
* route" rule before using it.
*/
inbox: InboxConfig | undefined;
/**
* True when the slug itself resolved — a stored inbox, an id fallback, or a system folder
* split. False for the `/mail/inbox` default and for non-inbox routes, which is what tells
* a caller whether the route's own query or a folder mapping is in play.
*/
matchedSlug: boolean;
/** Set when the slug names a hardcoded folder template (sent / archive / …), not an inbox. */
systemFolderSplit: SystemFolderSplit | undefined;
/**
* The persisted (or system-stub) record this route maps to. Only a record carries a CRM
* rule, so this — not `inbox` — is what may be sent as `inboxId`. A system folder split
* resolves to none.
*/
record: InboxConfig | undefined;
};
/**
* A custom inbox reached by its raw id, or by a slug that does not normalize to its name.
* `inbox` is excluded because it is the default route rather than a name to match.
*/
function findInboxByFolderSlug(folder: string, inboxes: InboxConfig[]): InboxConfig | undefined {
const normalizedFolder = folder.trim().toLowerCase();
if (!normalizedFolder || normalizedFolder === 'inbox') return undefined;
return inboxes.find(
(inbox) =>
!inbox.system &&
(slugifyInboxName(inbox.name) === normalizedFolder ||
inbox.id.toLowerCase() === normalizedFolder),
);
}
/** The inbox record backing a resolved inbox, by id and then by name for legacy stragglers. */
function findRecord(inbox: InboxConfig | undefined, inboxes: InboxConfig[]): InboxConfig | undefined {
if (!inbox) return undefined;
return inboxes.find(
(candidate) =>
candidate.id === inbox.id || candidate.name.toLowerCase() === inbox.name.toLowerCase(),
);
}
export function resolveRouteInbox(params: {
inboxes: InboxConfig[];
folder: string;
/**
* A caller that renders several inboxes under one slug (the stacked sections) names its
* own. It short-circuits the ladder rather than joining it — the URL is not describing it.
*/
explicitInbox?: InboxConfig;
}): RouteInboxResolution {
const { inboxes, folder, explicitInbox } = params;
if (explicitInbox) {
return {
inbox: explicitInbox,
matchedSlug: true,
systemFolderSplit: undefined,
record: findRecord(explicitInbox, inboxes),
};
}
const fromSlug = findInboxBySlug(inboxes, folder);
if (fromSlug) {
return {
inbox: fromSlug,
matchedSlug: true,
systemFolderSplit: undefined,
record: findRecord(fromSlug, inboxes),
};
}
const byId = findInboxByFolderSlug(folder, inboxes);
if (byId) {
return {
inbox: byId,
matchedSlug: true,
systemFolderSplit: undefined,
record: findRecord(byId, inboxes),
};
}
const systemFolderSplit = getSystemFolderSplit(folder);
if (systemFolderSplit) {
return {
// A folder template is not an inbox record — it is synthesised so every caller has one
// shape to read, and it deliberately resolves to no `record`: it carries no CRM rule.
inbox: {
id: systemFolderSplit.key,
name: systemFolderSplit.inboxName,
position: 0,
system: true,
rule: { kind: 'all' },
query: systemFolderSplit.compiledQuery,
},
matchedSlug: true,
systemFolderSplit,
record: undefined,
};
}
// `/mail/inbox` under a layout where no inbox owns that slug — the important/other split,
// where the folder route is already navigating to the first tab. The whole inbox is what
// the URL says while that redirect lands; the persisted active tab is a different question,
// and answering it here is what made the two lists disagree.
if (folder === 'inbox') {
const wholeInbox = inboxes.find((inbox) => inbox.id === DEFAULT_INBOX.id) ?? DEFAULT_INBOX;
return {
inbox: wholeInbox,
matchedSlug: false,
systemFolderSplit: undefined,
record: findRecord(wholeInbox, inboxes),
};
}
return { inbox: undefined, matchedSlug: false, systemFolderSplit: undefined, record: undefined };
}