/** * The ancestry of the current category, as data — and, given an ID, without * the catalogue. * * ── Two sources, and why the ID one is the good one ──────────────────────── * * `tn_ancestors_pks` on a row is the server's own chain, root-first. So the * whole breadcrumb of a category six levels deep is one 300-byte `GET {id}/` * for the row plus six more for the names — seven small answers that every * other surface on the page then finds in the cache. The catalogue that * answers the same question costs 1.4 MB and, on a live classified * deployment, twenty seconds. * * `slug` still walks the BUILT INDEX, because `stapel-categories` has no slug * lookup (`lookup_field` is never overridden, the list has no `slug` filter) * and a slug therefore cannot be resolved without the catalogue. That is a * contract gap, recorded in MODULE.md, not a preference — and it is why * `categoryId` is the mode a fast storefront routes through. * * ── Why the ID mode does not "walk the index" ────────────────────────────── * * The index version deliberately walked `tn_parent` through the built tree * rather than trusting `tn_ancestors_pks`, so that a crumb could never name a * category the visitor's own filters had removed. Without a tree there is * nothing to walk, so the server's column IS the chain — and the browse * predicate is applied to the FETCHED ROWS instead: an ancestor that is a * tombstone or inactive is dropped from the trail, which reaches the same * answer one row at a time. `test/tree.test.ts` still cross-checks the two * chains against each other on a fixture, so a divergence stays a red test. */ import type { ReactNode } from "react"; import type { LoadState } from "@stapel/core"; import type { Category } from "../api/types.js"; import type { CategoryLabel } from "../catalog/labels.js"; import type { CategoryBrowseOptions, UseCategoryCatalogOptions } from "../model/queries.js"; /** One crumb: the row, and how its caption must be rendered. */ export interface CategoryCrumb { readonly category: Category; /** `kind: "key"` → run `value` through the host's `t`. See * `catalog/labels.ts` — names on the wire are translation keys. */ readonly label: CategoryLabel; /** `true` for the last crumb — the page you are on. */ readonly isCurrent: boolean; /** * `false` when a skin must print this crumb as plain text — same * typography, no anchor — instead of a link. Default `true`; the current * crumb is always `true` here (`isCurrent` already keeps a skin from * linking it, so this field never has to say so a second time). * * Decided per crumb by {@link isWrapperAncestor} against the trail's own * rows unless {@link CategoryBreadcrumbsProps.unlink} is supplied, in which * case the host's predicate is the ONLY thing consulted — see that prop. */ readonly linked: boolean; } export interface CategoryBreadcrumbsBag { /** * Root → current, inclusive. `empty` means the selected category IS a root * (there is nothing above it), which is a different thing from a catalogue * that has not loaded and from a slug that does not exist. */ readonly state: LoadState; readonly unknownSlug: boolean; refetch(): void; } /** A crumb's row and caption, before {@link CategoryCrumb.linked} is decided. */ export type CategoryCrumbInput = Omit; export interface CategoryBreadcrumbsProps extends CategoryBrowseOptions, UseCategoryCatalogOptions { /** Resolved against the synced catalogue — the server has no slug lookup. */ slug?: string; /** The fast path: one small read per crumb, no catalogue. */ categoryId?: number | null; /** * Which crumbs to unlink, when the host holds knowledge the trail's own * rows cannot supply. Return `true` to print a crumb as plain text. * * Supplied, this REPLACES the automatic {@link isWrapperAncestor} check for * every crumb — a host that knows its own ancestry beats a guess made from * `children_pks` (or, on an older server, `tn_children_pks`) alone, and * running both would leave two authorities disagreeing about the same * crumb. Omitted, the automatic check runs. */ unlink?: (crumb: CategoryCrumbInput) => boolean; /** * Hold the trail that is on the glass while the NEXT category's rows are in * flight, instead of answering `loading`. Default `false` — a bar mounted * alone keeps the behaviour it has. * * A trail is one slice of a frame, and a screen that HOLDS its frame * (``) must not have a hole punched in it by the * one surface that did not: root → current is the cheapest part of the page * to keep and the most conspicuous part to lose. The kept answer arrives * stamped {@link LoadReady.refreshing}, so the skin can say so in place; a * REFUSAL is never held, and neither is a first load, which has nothing * behind it. See `useKeptLoad` in @stapel/core. */ keepPrevious?: boolean; children: (bag: CategoryBreadcrumbsBag) => ReactNode; } export declare function CategoryBreadcrumbs(props: CategoryBreadcrumbsProps): ReactNode; //# sourceMappingURL=CategoryBreadcrumbs.d.ts.map