@tangle-network/agent-app 0.45.63 → 0.45.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/session-shell/path.ts","../src/session-shell/nav-guard.ts","../src/session-shell/index.ts"],"sourcesContent":["/**\n * Path normalisation shared by the shell's routing helpers. Segment-aligned\n * comparison is the invariant: `/vault` must never claim `/vault-archive`, so\n * every prefix test here works on whole segments rather than string prefixes.\n */\n\nexport function stripTrailingSlashes(value: string): string {\n return value.replace(/\\/+$/, '')\n}\n\n/** Bare segment name, so a caller may pass `'/settings'` or `'settings'`. */\nexport function stripSlashes(value: string): string {\n return value.replace(/^\\/+|\\/+$/g, '')\n}\n\n/** Path with query + fragment removed and trailing slashes trimmed. A caller\n * passing a full href instead of a pathname would otherwise match nothing. */\nexport function normalizePath(pathname: string): string {\n const withoutHash = pathname.split('#')[0] ?? ''\n const withoutQuery = withoutHash.split('?')[0] ?? ''\n return stripTrailingSlashes(withoutQuery)\n}\n\n/** True when `path` is `prefix` or a segment-aligned descendant of it, so\n * `/vault` never claims `/vault-archive`. */\nexport function isUnderPrefix(path: string, prefix: string): boolean {\n const p = stripTrailingSlashes(prefix)\n if (p === '') return true\n return path === p || path.startsWith(`${p}/`)\n}\n\n/** Non-empty segments of a path or route pattern. Leading/trailing/duplicate\n * slashes collapse, so `/app//x/` and `app/x` compare equal. */\nexport function toSegments(value: string): string[] {\n return value.split('/').filter((segment) => segment.length > 0)\n}\n\n/** Canonical display form: rooted, no trailing slash, no empty segments. */\nexport function toRootedPath(value: string): string {\n return `/${toSegments(value).join('/')}`\n}\n","/**\n * Nav destinations, and the guard that proves every href the rail renders\n * resolves to a route the product's router actually registered.\n *\n * A rail row's href is assembled from a base plus a relative path, and nothing\n * downstream re-checks it: the sidebar renders a link, the click navigates, and\n * the router answers 404. A unit test written against the nav builder alone\n * cannot catch that — it asserts the href the builder produced, which is the\n * same wrong string the user clicks.\n *\n * Two mechanisms, meant to be used together:\n *\n * 1. `NavDestination` makes the base a REQUIRED discriminant (`scope`). An\n * optional `absolute?: boolean`-style flag has the opposite property:\n * omitting it type-checks, and the destination silently resolves under the\n * workspace prefix instead of the app-level one. A required literal union\n * turns that omission into a compile error, and widening `TScope` demands a\n * base for the new scope rather than defaulting to a wrong one.\n * 2. `assertNavHrefsRegistered` matches every resolved href against the route\n * table, so a destination the router never registered fails a test instead\n * of a user's click. It reads the product's real route table, so it cannot\n * agree with the builder's mistake the way a hand-maintained expected-href\n * list does.\n */\n\nimport { isUnderPrefix, normalizePath, stripTrailingSlashes, toRootedPath, toSegments } from './path'\n\n// ---------------------------------------------------------------------------\n// Destinations — the base is a required discriminant, never an optional flag\n// ---------------------------------------------------------------------------\n\n/** The bases a product routes rail rows under. `workspace` is the per-workspace\n * prefix (`/app/ws_123`); `app` is the account-level one (`/app`), where\n * singleton surfaces such as a shared terminal or billing live. */\nexport type NavScope = 'workspace' | 'app'\n\n/** A base path per scope. Widening `TScope` widens this record, so a product\n * that adds a scope cannot compile until it supplies that scope's base. */\nexport type NavScopeBases<TScope extends string = NavScope> = Readonly<Record<TScope, string>>\n\n/** One rail destination as the product declares it, before a base is applied. */\nexport interface NavDestination<TScope extends string = NavScope> {\n id: string\n /** Path relative to the base named by `scope`. `''` is the base itself.\n * Must be empty or start with `/` — a bare `'vault'` would concatenate into\n * `/app/ws_123vault`, so it is rejected rather than silently repaired. */\n path: string\n /** Which base `path` resolves against. Required on purpose. */\n scope: TScope\n}\n\n/** A destination with its base applied. */\nexport interface ResolvedNavDestination<TScope extends string = NavScope> {\n id: string\n href: string\n scope: TScope\n}\n\n/** Apply a destination's scope base to its path.\n *\n * Throws when the scope has no base configured — a product that assembles\n * `bases` dynamically can defeat the type-level guarantee, and a missing base\n * would otherwise produce `undefined/vault`. */\nexport function resolveNavHref<TScope extends string>(\n destination: NavDestination<TScope>,\n bases: NavScopeBases<TScope>,\n): string {\n const base = bases[destination.scope]\n if (typeof base !== 'string') {\n throw new Error(\n `Nav destination '${destination.id}' uses scope '${destination.scope}', which has no configured base`,\n )\n }\n if (destination.path !== '' && !destination.path.startsWith('/')) {\n throw new Error(\n `Nav destination '${destination.id}' path must be empty or start with '/' (got '${destination.path}')`,\n )\n }\n const rooted = `${stripTrailingSlashes(base)}${destination.path}`\n return rooted === '' ? '/' : stripTrailingSlashes(rooted)\n}\n\n/** Apply the bases to every destination, preserving declaration order. */\nexport function resolveNavDestinations<TScope extends string>(\n destinations: readonly NavDestination<TScope>[],\n bases: NavScopeBases<TScope>,\n): ResolvedNavDestination<TScope>[] {\n return destinations.map((destination) => ({\n id: destination.id,\n href: resolveNavHref(destination, bases),\n scope: destination.scope,\n }))\n}\n\nexport interface ResolveScopedActiveNavIdOptions<TScope extends string = NavScope> {\n pathname: string\n destinations: readonly NavDestination<TScope>[]\n bases: NavScopeBases<TScope>\n /** Extra ABSOLUTE prefixes that light an existing row, e.g.\n * `{ '/app/ws_1/agents': 'integrations' }`. Same longest-prefix contest. */\n aliases?: Readonly<Record<string, string>>\n /** ABSOLUTE prefixes that deliberately highlight nothing, beating any shorter\n * match. */\n claimsNothing?: readonly string[]\n}\n\n/**\n * The rail row to highlight, across scopes.\n *\n * `resolveActiveNavId` resolves rows against ONE base, so an app-level row can\n * only be highlighted by a second, hand-rolled scan — the same split that lets\n * an app-level destination render under the workspace base. This resolves the\n * hrefs first and runs a single longest-prefix contest over absolute paths, so\n * declaration order cannot change the answer and no scope needs its own pass.\n *\n * Prefixes in `aliases` / `claimsNothing` are absolute here, unlike\n * `resolveActiveNavId`'s base-relative ones, because the contest itself is\n * absolute.\n */\nexport function resolveScopedActiveNavId<TScope extends string>({\n pathname,\n destinations,\n bases,\n aliases,\n claimsNothing,\n}: ResolveScopedActiveNavIdOptions<TScope>): string | undefined {\n const path = normalizePath(pathname)\n let bestLength = -1\n let bestId: string | undefined\n const consider = (candidate: string, id: string | undefined, winsTies = false): void => {\n const full = stripTrailingSlashes(candidate)\n if (!isUnderPrefix(path, full)) return\n if (full.length > bestLength || (winsTies && full.length === bestLength)) {\n bestLength = full.length\n bestId = id\n }\n }\n for (const resolved of resolveNavDestinations(destinations, bases)) consider(resolved.href, resolved.id)\n for (const [prefix, id] of Object.entries(aliases ?? {})) consider(prefix, id)\n // Declared last and wins an exact-length tie: naming a prefix here is a\n // deliberate override of the row that owns it.\n for (const prefix of claimsNothing ?? []) consider(prefix, undefined, true)\n return bestId\n}\n\n// ---------------------------------------------------------------------------\n// Route table — structurally the product's own router config\n// ---------------------------------------------------------------------------\n\n/**\n * One entry of a registered route table. Structurally compatible with\n * react-router's `RouteConfigEntry`, so a product passes its real `routes.ts`\n * default export straight in — the point of the guard is that it reads the\n * router's own truth rather than a second list that can agree with the bug.\n */\nexport interface RegisteredRoute {\n /** Absent on a pathless layout route: its children inherit the parent path. */\n path?: string\n index?: boolean\n children?: readonly RegisteredRoute[]\n}\n\n/** A route table entry is either a bare pattern string or a router config node. */\nexport type NavRouteTable = readonly (string | RegisteredRoute)[]\n\nfunction joinPattern(parent: string, child: string): string {\n if (child.startsWith('/')) return child\n if (child === '') return parent\n return `${parent}/${child}`\n}\n\n/**\n * Every path pattern the table registers, rooted and de-duplicated.\n *\n * Parent nodes contribute their own cumulative path as well as their children's:\n * a router matches a parent route with an index child at the parent path, and a\n * parent without one still matches with an empty outlet, so treating parents as\n * unregistered would flag working hrefs.\n */\nexport function flattenRouteTable(table: NavRouteTable): string[] {\n const patterns: string[] = []\n const walk = (entries: NavRouteTable, parent: string): void => {\n for (const entry of entries) {\n if (typeof entry === 'string') {\n patterns.push(toRootedPath(joinPattern(parent, entry)))\n continue\n }\n const own = entry.path === undefined ? parent : joinPattern(parent, entry.path)\n patterns.push(toRootedPath(own))\n if (entry.children) walk(entry.children, own)\n }\n }\n walk(table, '')\n return [...new Set(patterns)]\n}\n\n/**\n * Whole-path segment match of a concrete path against one route pattern.\n *\n * Supports the three pattern forms a router uses: literal segments, `:param`\n * (exactly one non-empty segment), optional `:param?` / `segment?` (zero or\n * one), and a trailing `*` splat (zero or more). Matching is recursive because\n * an optional segment forks the walk — a linear scan silently mismatches\n * `/a/b` against `a/:x?/b`.\n */\nfunction matchesPattern(pathSegments: readonly string[], patternSegments: readonly string[], caseSensitive: boolean): boolean {\n if (patternSegments.length === 0) return pathSegments.length === 0\n const head = patternSegments[0] ?? ''\n if (head === '*') return true\n const rest = patternSegments.slice(1)\n const optional = head.endsWith('?')\n const core = optional ? head.slice(0, -1) : head\n const first = pathSegments[0]\n if (first !== undefined) {\n const hit = core.startsWith(':')\n ? first.length > 0\n : caseSensitive\n ? core === first\n : core.toLowerCase() === first.toLowerCase()\n if (hit && matchesPattern(pathSegments.slice(1), rest, caseSensitive)) return true\n }\n return optional ? matchesPattern(pathSegments, rest, caseSensitive) : false\n}\n\n// ---------------------------------------------------------------------------\n// The guard\n// ---------------------------------------------------------------------------\n\n/**\n * A nav row as the guard needs to see it. Structurally satisfied by\n * `SessionRailNavItem` / `SessionRailSubItem` and by sandbox-ui's\n * `SidebarLayoutNavItem`, so the guard runs over the builder's real output\n * rather than a re-declaration of it.\n */\nexport interface NavHrefItem {\n id: string\n href: string\n subItems?: readonly NavHrefItem[]\n}\n\nexport type NavHrefProblemReason =\n /** No registered pattern matches the resolved href. */\n | 'unregistered'\n /** Empty, fragment-only, or not rooted at `/` — the row navigates nowhere\n * predictable regardless of the route table. */\n | 'not-a-path'\n /** Leaves the router (scheme or protocol-relative) while `allowExternal` is\n * off. */\n | 'external'\n\nexport interface NavHrefProblem {\n id: string\n href: string\n reason: NavHrefProblemReason\n /** Registered patterns ending in the same segment. A destination resolved\n * under the wrong base lands here as its correctly-based twin, which is what\n * names the missing scope in the failure message. */\n nearest: string[]\n message: string\n}\n\nexport interface NavHrefReport {\n /** Hrefs examined, including nested sub-items. */\n checked: number\n problems: NavHrefProblem[]\n /** Off-router destinations accepted because `allowExternal` is on. */\n external: string[]\n /** The flattened route table the check ran against. */\n patterns: string[]\n}\n\nexport interface NavHrefCheckOptions {\n /** Hrefs to skip, compared after query/fragment removal. For a destination\n * served outside this route table (a static asset, another worker). */\n ignore?: readonly string[]\n /** Absolute URLs / `mailto:` / `tel:` are reported under `external` instead\n * of failing. Default true. */\n allowExternal?: boolean\n /** Compare literal segments case-sensitively. Default true — a router that\n * matches case-insensitively still renders a link the deploy's CDN or a\n * case-sensitive origin may not. */\n caseSensitive?: boolean\n}\n\n/** `scheme:` or `//host` — anything the router will not resolve as a path. */\nconst OFF_ROUTER_HREF = /^(?:[a-z][a-z0-9+.-]*:|\\/\\/)/i\n\nfunction flattenItems(items: readonly NavHrefItem[], out: NavHrefItem[] = []): NavHrefItem[] {\n for (const item of items) {\n out.push(item)\n if (item.subItems) flattenItems(item.subItems, out)\n }\n return out\n}\n\n/**\n * Check every nav href against the product's route table.\n *\n * Pure — returns the full report so a caller can assert on parts of it. Use\n * {@link assertNavHrefsRegistered} in tests; it turns the report into a failure\n * that names the offending row, its resolved href, and the near-miss pattern.\n */\nexport function checkNavHrefs(\n items: readonly NavHrefItem[],\n routes: NavRouteTable,\n options: NavHrefCheckOptions = {},\n): NavHrefReport {\n const { ignore, allowExternal = true, caseSensitive = true } = options\n const patterns = flattenRouteTable(routes)\n const patternSegments = patterns.map((pattern) => ({ pattern, segments: toSegments(pattern) }))\n const ignored = new Set((ignore ?? []).map((href) => normalizePath(href)))\n const problems: NavHrefProblem[] = []\n const external: string[] = []\n const flat = flattenItems(items)\n let checked = 0\n\n for (const item of flat) {\n const raw = item.href\n const path = normalizePath(raw)\n if (ignored.has(path)) continue\n if (OFF_ROUTER_HREF.test(raw)) {\n if (allowExternal) {\n external.push(raw)\n continue\n }\n // Counted as checked: it was examined and rejected, so the vacuous-pass\n // guard must not read this run as \"nothing was looked at\".\n checked += 1\n problems.push({\n id: item.id,\n href: raw,\n reason: 'external',\n nearest: [],\n message: `Nav item '${item.id}' href '${raw}' leaves the router, and external destinations are rejected`,\n })\n continue\n }\n checked += 1\n if (raw === '' || raw.startsWith('#') || !raw.startsWith('/')) {\n problems.push({\n id: item.id,\n href: raw,\n reason: 'not-a-path',\n nearest: [],\n message: `Nav item '${item.id}' href '${raw}' is not a rooted path — it cannot resolve to a registered route`,\n })\n continue\n }\n const segments = toSegments(path)\n if (patternSegments.some((candidate) => matchesPattern(segments, candidate.segments, caseSensitive))) continue\n const nearest = nearestPatterns(segments, patternSegments.map((candidate) => candidate.pattern), caseSensitive)\n problems.push({\n id: item.id,\n href: raw,\n reason: 'unregistered',\n nearest,\n message:\n `Nav item '${item.id}' href '${raw}' matches no registered route` +\n (nearest.length ? ` — nearest registered: ${nearest.join(', ')}` : ''),\n })\n }\n\n return { checked, problems, external, patterns }\n}\n\n/** Registered patterns whose last segment equals the href's last segment: the\n * same destination under a different base is the near-miss worth printing. */\nfunction nearestPatterns(segments: readonly string[], patterns: readonly string[], caseSensitive: boolean): string[] {\n const tail = segments[segments.length - 1]\n if (tail === undefined) return []\n const same = (a: string, b: string): boolean => (caseSensitive ? a === b : a.toLowerCase() === b.toLowerCase())\n return patterns\n .filter((pattern) => {\n const patternTail = toSegments(pattern).at(-1)\n return patternTail !== undefined && same(patternTail, tail)\n })\n .slice(0, 5)\n}\n\n/**\n * Fail unless every nav href resolves to a registered route.\n *\n * Throws on an empty item list or an empty route table as well: a guard that\n * examined nothing reports safety it does not provide, and both are what a\n * mis-wired import looks like.\n */\nexport function assertNavHrefsRegistered(\n items: readonly NavHrefItem[],\n routes: NavRouteTable,\n options: NavHrefCheckOptions = {},\n): void {\n if (items.length === 0) {\n throw new Error('assertNavHrefsRegistered received no nav items — the check would pass without examining anything')\n }\n const report = checkNavHrefs(items, routes, options)\n if (report.patterns.length === 0) {\n throw new Error('assertNavHrefsRegistered received an empty route table — every href would fail or nothing would be proven')\n }\n // Real problems are reported before the vacuous-pass guard: rejected external\n // hrefs are problems that were never \"checked\", and the guard's message would\n // otherwise hide them.\n if (report.problems.length > 0) {\n const detail = report.problems.map((problem) => ` - ${problem.message}`).join('\\n')\n throw new Error(\n `${report.problems.length} of ${report.checked} nav hrefs do not resolve to a registered route:\\n${detail}\\n` +\n `Registered patterns (${report.patterns.length}): ${report.patterns.join(', ')}`,\n )\n }\n if (report.checked === 0) {\n throw new Error(\n `assertNavHrefsRegistered examined 0 hrefs (${report.external.length} external, ${ignoredCount(items, options)} ignored) — the check would pass without examining anything`,\n )\n }\n}\n\nfunction ignoredCount(items: readonly NavHrefItem[], options: NavHrefCheckOptions): number {\n const ignored = new Set((options.ignore ?? []).map((href) => normalizePath(href)))\n return flattenItems(items).filter((item) => ignored.has(normalizePath(item.href))).length\n}\n","/**\n * Session shell — the app-shell mechanism every agent product needs around the\n * chat surface: a list of past sessions in the rail, an entry point for a new\n * one, and a paged history view behind it.\n *\n * `/web-react` already owns the chat SURFACE (composer, transcript, cards); it\n * owned no session SHELL, so all four products hand-rolled one and drifted.\n * This module is the shell's pure half: no React, no DOM, no peer imports, so a\n * server loader can call `readRailCollapsedCookie` without dragging React into\n * a worker bundle (`/web-react` holds the rendered half).\n *\n * Domain stays a parameter. A \"session\" here is only an id, a title and a\n * timestamp — a gtm thread, a tax session and a legal matter are all the same\n * shape to the shell, and the product supplies routing through `hrefForSession`\n * rather than the shell knowing any URL.\n */\n\nimport { isUnderPrefix, normalizePath, stripSlashes, stripTrailingSlashes } from './path'\n\nexport * from './nav-guard'\n\n/** One session as the shell needs to see it. Products map their own row\n * (thread / session / matter) onto this before handing it over. */\nexport interface SessionSummary {\n id: string\n /** `null`/empty renders as the untitled placeholder rather than a blank row. */\n title: string | null\n /** ISO-8601. `null` when the product has no timestamp to show. */\n updatedAt: string | null\n isPinned?: boolean\n /** Unread for the viewer. Use `resolveSessionUnread` to fold live overlays in. */\n unread?: boolean\n /** Free-form product label (gtm categories, legal matter types). Passed\n * through untouched — the shell never interprets it. */\n category?: string | null\n}\n\n/** One fetched page of sessions with an optional continuation cursor. */\nexport interface SessionPage {\n items: SessionSummary[]\n /** Opaque continuation token; absent/null ⇒ no further pages. */\n nextCursor?: string | null\n}\n\n/** Sort order for the history view. The product's fetcher decides what these\n * mean against its own storage; the shell only round-trips the value. */\nexport type SessionSort = 'newest' | 'oldest'\n\n// ---------------------------------------------------------------------------\n// Rail items — structurally assignable to sandbox-ui's SidebarLayout types\n// ---------------------------------------------------------------------------\n\n/**\n * These mirror `@tangle-network/sandbox-ui/dashboard`'s `SidebarLayoutNavItem`\n * / `RailExpandableSubItem` STRUCTURALLY rather than importing them, so this\n * module stays free of the optional peer (invariant 3 — structural over\n * hard-dep when the surface is small). `tests/session-shell/rail-contract.test.ts`\n * assigns the builder output to the real sandbox-ui types, so a drift in either\n * direction fails CI instead of silently dropping a field at runtime.\n *\n * `TIcon` is the product's icon component type (lucide, custom, anything) —\n * generic so this file needs no React types.\n */\nexport interface SessionRailAction<TIcon = unknown> {\n id: string\n label: string\n icon?: TIcon\n destructive?: boolean\n onSelect: () => void\n}\n\nexport type RailPrefetch = 'none' | 'intent' | 'render' | 'viewport'\n\nexport interface SessionRailSubItem<TIcon = unknown> {\n id: string\n label: string\n href: string\n prefetch?: RailPrefetch\n /** Live working indicator — the session is mid-turn. */\n isLoading?: boolean\n /** Bold + leading dot. sandbox-ui suppresses it while `isLoading`. */\n unread?: boolean\n /** Emphasised row, used for the trailing \"view all\" overflow link. */\n emphasis?: boolean\n actions?: SessionRailAction<TIcon>[]\n}\n\nexport interface SessionRailNavItem<TIcon = unknown> {\n id: string\n /** REQUIRED, mirroring sandbox-ui — the rail renders `<Icon />` unguarded, so\n * an omitted icon is a blank/crashing row rather than a styling nit. */\n icon: TIcon\n label: string\n href: string\n badge?: number\n expandable?: boolean\n defaultOpen?: boolean\n subItems?: SessionRailSubItem<TIcon>[]\n subActiveIds?: string[]\n emptyLabel?: string\n prefetch?: RailPrefetch\n}\n\n/** Per-row rename/delete wiring. Supplied by the layout that owns the dialogs;\n * omitted (or `canEdit: false`) leaves rows read-only. */\nexport interface SessionRowActions<TIcon = unknown> {\n canEdit: boolean\n renameIcon?: TIcon\n deleteIcon?: TIcon\n renameLabel?: string\n deleteLabel?: string\n /**\n * Omit when the product cannot rename a session — the row then offers delete\n * only, instead of a menu item that does nothing.\n *\n * Independently optional, matching `SessionHistoryPanel`, which has always\n * rendered whichever of the two it was given. The rail builder used to demand\n * both, so a product with archive-but-no-rename (tax) could either fake a\n * rename or ship no row actions at all.\n */\n onRename?: (session: SessionSummary) => void\n onDelete?: (session: SessionSummary) => void\n /**\n * Row actions this shell has no opinion about — pin, categorise, duplicate,\n * share. Evaluated per session so a label can read that row's state\n * (\"Pin\" vs \"Unpin\"), and ordered between rename and delete so the\n * destructive action stays last.\n *\n * `id` must not be `rename` or `delete`; those are the shell's own.\n */\n extraActions?: (session: SessionSummary) => SessionRailAction<TIcon>[]\n}\n\nexport const UNTITLED_SESSION_LABEL = 'Untitled chat'\n\n/** Display title for a session row — trims, and falls back rather than\n * rendering an empty row the user cannot aim at. */\nexport function sessionLabel(session: SessionSummary, untitled = UNTITLED_SESSION_LABEL): string {\n return session.title?.trim() || untitled\n}\n\nexport interface BuildSessionSubItemsOptions<TIcon = unknown> {\n sessions: SessionSummary[]\n /** The product's route for one session. The shell never builds a URL itself. */\n hrefForSession: (sessionId: string) => string\n /** Ids currently mid-turn — renders the working indicator. */\n respondingSessionIds?: ReadonlySet<string>\n actions?: SessionRowActions<TIcon>\n untitledLabel?: string\n prefetch?: RailPrefetch\n /** Trailing \"view all\" row, appended when the capped list hides sessions. */\n overflow?: { href: string; label?: string }\n}\n\n/** Session rows for the rail's expandable history item. */\nexport function buildSessionSubItems<TIcon = unknown>({\n sessions,\n hrefForSession,\n respondingSessionIds,\n actions,\n untitledLabel = UNTITLED_SESSION_LABEL,\n prefetch = 'intent',\n overflow,\n}: BuildSessionSubItemsOptions<TIcon>): SessionRailSubItem<TIcon>[] {\n /**\n * Only the handlers the product actually supplied. An empty result becomes\n * `undefined` rather than `[]`, because sandbox-ui renders the kebab trigger\n * whenever `actions` is an array — an empty one is a button that opens an\n * empty menu.\n */\n const rowActions = (session: SessionSummary): SessionRailAction<TIcon>[] | undefined => {\n if (!actions?.canEdit) return undefined\n const built: SessionRailAction<TIcon>[] = []\n const { onRename, onDelete, extraActions } = actions\n if (onRename) {\n built.push({\n id: 'rename',\n label: actions.renameLabel ?? 'Rename',\n icon: actions.renameIcon,\n onSelect: () => onRename(session),\n })\n }\n if (extraActions) built.push(...extraActions(session))\n if (onDelete) {\n built.push({\n id: 'delete',\n label: actions.deleteLabel ?? 'Delete',\n icon: actions.deleteIcon,\n destructive: true,\n onSelect: () => onDelete(session),\n })\n }\n return built.length ? built : undefined\n }\n\n const rows: SessionRailSubItem<TIcon>[] = sessions.map((session) => ({\n id: session.id,\n label: sessionLabel(session, untitledLabel),\n href: hrefForSession(session.id),\n prefetch,\n isLoading: respondingSessionIds?.has(session.id) ?? false,\n unread: Boolean(session.unread),\n actions: rowActions(session),\n }))\n if (!overflow) return rows\n return [\n ...rows,\n {\n id: 'view-all',\n label: overflow.label ?? 'View all chats',\n href: overflow.href,\n prefetch,\n emphasis: true,\n },\n ]\n}\n\nexport interface BuildSessionNavItemOptions<TIcon = unknown>\n extends BuildSessionSubItemsOptions<TIcon> {\n /** Nav id the product highlights against (`activeNavId === id`). */\n id?: string\n label?: string\n /** The product's icon component. Required — see `SessionRailNavItem.icon`. */\n icon: TIcon\n /** The expandable row's own destination — the full history page. */\n href: string\n /** Session currently open, highlighted inside the expandable. */\n activeSessionId?: string | null\n emptyLabel?: string\n defaultOpen?: boolean\n}\n\n/**\n * The rail's session entry: one expandable nav row whose sub-items are the\n * recent sessions. This is the structure the owner asked for — history lives IN\n * the rail, not in a second sidebar panel beside it.\n */\nexport function buildSessionNavItem<TIcon = unknown>({\n id = 'history',\n label = 'History',\n icon,\n href,\n activeSessionId,\n emptyLabel = 'No chats yet',\n defaultOpen = true,\n ...subItemOptions\n}: BuildSessionNavItemOptions<TIcon>): SessionRailNavItem<TIcon> {\n return {\n id,\n icon,\n label,\n href,\n expandable: true,\n defaultOpen,\n subItems: buildSessionSubItems<TIcon>(subItemOptions),\n subActiveIds: activeSessionId ? [activeSessionId] : undefined,\n emptyLabel,\n prefetch: subItemOptions.prefetch ?? 'intent',\n }\n}\n\n// ---------------------------------------------------------------------------\n// Routing / selection\n// ---------------------------------------------------------------------------\n\nexport interface ActiveSessionIdOptions {\n pathname: string\n /** Workspace-scoped route base, e.g. `/app/ws_123`. */\n base: string\n /** Route segment sessions live under. Default `chat` ⇒ `${base}/chat/:id`.\n * Pass `''` when sessions sit DIRECTLY under the base (`/app/:sessionId`),\n * which is how one product routes them — then `reserved` is mandatory. */\n segment?: string\n /** Segment that means \"composing a new session\", not an id. Default `new`. */\n newSegment?: string\n /**\n * First segments that are OTHER routes, not session ids. Only meaningful\n * with `segment: ''`, where `/app/settings` is otherwise indistinguishable\n * from a session called `settings` — and resolving it as one would highlight\n * and prefetch a session that does not exist. Pass the product's own nav\n * paths; unknown-but-reserved is a routing bug, so this fails closed.\n */\n reserved?: readonly string[]\n}\n\n/**\n * The session id the current route has open, or `null` on the new-session\n * composer / anywhere else.\n *\n * Anchored at `base` on purpose. A bare `/\\/chat\\/([^/]+)/` scan — the shape\n * three products shipped — matches the FIRST `/chat/` anywhere in the path, so\n * a workspace or vault folder named `chat` resolves a neighbouring segment as a\n * session id and the rail highlights (and prefetches) a session the user is not\n * in. Same class as attaching to a stale box: it looks right and points at the\n * wrong row.\n */\nexport function activeSessionIdFromPath({\n pathname,\n base,\n segment = 'chat',\n newSegment = 'new',\n reserved,\n}: ActiveSessionIdOptions): string | null {\n const path = normalizePath(pathname)\n const root = stripTrailingSlashes(base)\n const prefix = segment ? `${root}/${segment}` : root\n if (!isUnderPrefix(path, prefix) || path === prefix) return null\n const id = path.slice(prefix.length + 1).split('/')[0] ?? ''\n if (!id || id === newSegment) return null\n // `/app/settings` under a segment-less route is a sibling page, not a\n // session named \"settings\".\n if (reserved?.some((name) => stripSlashes(name) === id)) return null\n return decodeURIComponent(id)\n}\n\n/** One rail destination. `path` is relative to the workspace base. */\nexport interface NavRouteDef {\n id: string\n path: string\n}\n\nexport interface ResolveActiveNavIdOptions {\n pathname: string\n base: string\n /** The product's rail rows, in any order — resolution is longest-prefix. */\n routes: NavRouteDef[]\n /** Extra prefixes that light an existing row: `{ '/agents': 'integrations' }`.\n * Participates in the same longest-prefix contest. */\n aliases?: Record<string, string>\n /** Prefixes that deliberately highlight NOTHING, beating any shorter match.\n * gtm uses this so an open chat lights no rail row while `/chat/new` still\n * lights \"New\". */\n claimsNothing?: string[]\n}\n\n/**\n * The rail row to highlight for the current route.\n *\n * Longest-prefix wins, so declaration order cannot change the answer. The\n * per-product versions this replaces were first-match over an array, which made\n * `/chat/new` vs `/chat` an ordering accident rather than a rule.\n */\nexport function resolveActiveNavId({\n pathname,\n base,\n routes,\n aliases,\n claimsNothing,\n}: ResolveActiveNavIdOptions): string | undefined {\n const path = normalizePath(pathname)\n const root = stripTrailingSlashes(base)\n let bestLength = -1\n let bestId: string | undefined\n const consider = (relative: string, id: string | undefined, winsTies = false) => {\n const full = stripTrailingSlashes(`${root}${relative}`)\n if (!isUnderPrefix(path, full)) return\n if (full.length > bestLength || (winsTies && full.length === bestLength)) {\n bestLength = full.length\n bestId = id\n }\n }\n for (const route of routes) consider(route.path, route.id)\n for (const [prefix, id] of Object.entries(aliases ?? {})) consider(prefix, id)\n // Declared last and wins an exact-length tie: naming a prefix in\n // `claimsNothing` is a deliberate override of the row that owns it, so\n // `claimsNothing: ['/chat']` beats a `{ id: 'chat', path: '/chat' }` row while\n // a longer `/chat/new` still wins on specificity.\n for (const prefix of claimsNothing ?? []) consider(prefix, undefined, true)\n return bestId\n}\n\n// ---------------------------------------------------------------------------\n// Sidebar list composition\n// ---------------------------------------------------------------------------\n\nexport interface ResolveSessionUnreadOptions {\n sessionId: string\n /** Server-computed unread from the route loader. */\n loaderUnread: boolean\n /** Live \"went unread\" ids from the workspace channel. */\n liveUnreadIds?: ReadonlySet<string>\n /** Ids this tab has already opened since the loader ran. */\n locallyReadIds?: ReadonlySet<string>\n /** The open session is never unread to its own viewer. */\n currentSessionId?: string | null\n}\n\n/**\n * Effective unread for one row. The loader's value can be stale — a layout\n * loader that survives same-workspace navigation keeps reporting a session as\n * unread after the user opened it — so live and local overlays win over it, and\n * the currently-open session always reads as read.\n */\nexport function resolveSessionUnread({\n sessionId,\n loaderUnread,\n liveUnreadIds,\n locallyReadIds,\n currentSessionId,\n}: ResolveSessionUnreadOptions): boolean {\n if (sessionId === currentSessionId) return false\n if (liveUnreadIds?.has(sessionId)) return true\n if (locallyReadIds?.has(sessionId)) return false\n return loaderUnread\n}\n\nexport interface ComposeSidebarSessionsOptions {\n /** Server-rendered rows, already ordered by the product's query. */\n loaderSessions: SessionSummary[]\n /** Optimistic rows from the live channel (a chat created in another tab). */\n optimisticSessions?: SessionSummary[]\n /** Rail cap. The full list lives on the history page. */\n limit: number\n /** Total sessions the product holds, used to decide the overflow row. */\n totalCount?: number\n liveUnreadIds?: ReadonlySet<string>\n locallyReadIds?: ReadonlySet<string>\n currentSessionId?: string | null\n}\n\nexport interface ComposedSidebarSessions {\n sessions: SessionSummary[]\n /** More sessions exist than the rail shows ⇒ render the \"view all\" row. */\n hasMore: boolean\n}\n\n/**\n * The rail's session list: optimistic rows first, then the loader's, capped,\n * with unread resolved per row.\n *\n * Optimistic rows are deduped against the loader by id — once a revalidation\n * brings a live-created session back from the server it must not appear twice\n * (duplicate React keys, and the row's actions would target the same session\n * from two places).\n */\nexport function composeSidebarSessions({\n loaderSessions,\n optimisticSessions = [],\n limit,\n totalCount,\n liveUnreadIds,\n locallyReadIds,\n currentSessionId,\n}: ComposeSidebarSessionsOptions): ComposedSidebarSessions {\n const loaderIds = new Set(loaderSessions.map((session) => session.id))\n const pendingNew = optimisticSessions.filter((session) => !loaderIds.has(session.id))\n const merged = [...pendingNew, ...loaderSessions]\n const sessions = merged.slice(0, Math.max(0, limit)).map((session) => ({\n ...session,\n unread: resolveSessionUnread({\n sessionId: session.id,\n loaderUnread: Boolean(session.unread),\n liveUnreadIds,\n locallyReadIds,\n currentSessionId,\n }),\n }))\n const known = (totalCount ?? loaderSessions.length) + pendingNew.length\n return { sessions, hasMore: known > sessions.length }\n}\n\n/**\n * Append a fetched page to held rows, dropping ids already shown. A session\n * bumped to the top between two page fetches otherwise arrives twice — once in\n * the page it moved out of and once in the page it moved into.\n */\nexport function mergeSessionPages(\n existing: SessionSummary[],\n incoming: SessionSummary[],\n): SessionSummary[] {\n const seen = new Set(existing.map((session) => session.id))\n return [...existing, ...incoming.filter((session) => !seen.has(session.id))]\n}\n\n// ---------------------------------------------------------------------------\n// Rail collapse cookie (SSR-seeded so the first paint matches the client)\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_RAIL_COOKIE_NAME = 'agent-sidebar-rail-collapsed'\n\n/**\n * Read the persisted rail-collapse state from a request's `Cookie` header, so\n * the server renders the rail in the state the user left it and the first\n * client render does not re-flow.\n *\n * Parses the header rather than building a `RegExp` from the cookie name (the\n * shape the products shipped): a name containing a regex metacharacter would\n * silently match the wrong cookie or none at all.\n */\nexport function readRailCollapsedCookie(\n cookieHeader: string | null | undefined,\n name: string = DEFAULT_RAIL_COOKIE_NAME,\n): boolean {\n for (const pair of (cookieHeader ?? '').split(';')) {\n const eq = pair.indexOf('=')\n if (eq === -1) continue\n if (pair.slice(0, eq).trim() !== name) continue\n return pair.slice(eq + 1).trim() === '1'\n }\n return false\n}\n\nexport interface RailCookieOptions {\n name?: string\n /** Seconds. Default one year. */\n maxAge?: number\n path?: string\n /** Omit to auto-detect: `secure` on https, off on http://localhost — a Secure\n * cookie is dropped there and the rail state would not persist in dev. */\n secure?: boolean\n}\n\n/** The cookie string for a collapse state. Usable as `document.cookie` or as a\n * `Set-Cookie` value. Exported separately so it is testable without a DOM. */\nexport function railCollapsedCookie(\n collapsed: boolean,\n { name = DEFAULT_RAIL_COOKIE_NAME, maxAge = 31_536_000, path = '/', secure }: RailCookieOptions = {},\n): string {\n const isSecure =\n secure ?? (typeof location !== 'undefined' && location.protocol === 'https:')\n return `${name}=${collapsed ? '1' : '0'}; path=${path}; max-age=${maxAge}; samesite=lax${isSecure ? '; secure' : ''}`\n}\n\n/** Persist the rail-collapse state from the browser. No-op without a document\n * so a shared toggle handler is safe to call during SSR. */\nexport function writeRailCollapsedCookie(collapsed: boolean, options: RailCookieOptions = {}): void {\n if (typeof document === 'undefined') return\n document.cookie = railCollapsedCookie(collapsed, options)\n}\n"],"mappings":"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","names":[]}