{"version":3,"file":"session-identity.d.ts","sourceRoot":"","sources":["../../src/core/session-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,0FAA0F;AAC1F,eAAO,MAAM,mBAAmB,IAAI,CAAC;AA8GrC,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,iFAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,gEAAgE;AAChE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAiCD,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAAmD,CAAC;AAEzG,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEjE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASvE;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,CAIhG","sourcesContent":["/**\n * A session's visual identity: the name it carries before anyone names it, and\n * the palette slot its chip is filled with.\n *\n * Both are pure functions of the session id, which means they need no storage of\n * their own: the id is already in the session header, so a resumed session comes\n * back wearing the same slug and the same colour, and a fork — which mints a new\n * id — gets its own identity instead of quietly wearing its parent's. What *is*\n * stored is an override: `/name` and `/color` write to the session's\n * `session_info` entry, and those win over everything here (see\n * SessionManager.getDisplayName / getSessionColorSlot).\n *\n * This module is deliberately theme-free. It deals in slot *numbers*; mapping a\n * slot onto a colour token is the theme's job, so `core/` never has to reach into\n * a mode's rendering code.\n */\n\n/** Number of colour slots a session can land in. Matches the theme's identity palette. */\nexport const SESSION_COLOR_SLOTS = 6;\n\n/**\n * The slug vocabulary. Chosen for the terminal, not for poetry: every word is\n * short (so a two-word slug stays well inside the chip's 20-column cap), lower\n * case, unambiguous when read at a glance from across a desk, and free of the\n * near-homographs that make two sessions hard to tell apart (no \"lake\"/\"lane\").\n * 32 × 32 gives 1024 combinations, which is far more than the number of sessions\n * anyone has open at once — the point is recognition, not uniqueness.\n */\nconst ADJECTIVES = [\n\t\"amber\",\n\t\"ash\",\n\t\"azure\",\n\t\"brisk\",\n\t\"bronze\",\n\t\"calm\",\n\t\"clay\",\n\t\"coral\",\n\t\"crisp\",\n\t\"dusk\",\n\t\"ember\",\n\t\"fern\",\n\t\"flint\",\n\t\"frost\",\n\t\"gold\",\n\t\"ivory\",\n\t\"jade\",\n\t\"lunar\",\n\t\"mellow\",\n\t\"mint\",\n\t\"olive\",\n\t\"onyx\",\n\t\"plum\",\n\t\"quiet\",\n\t\"rust\",\n\t\"sable\",\n\t\"sage\",\n\t\"slate\",\n\t\"solar\",\n\t\"swift\",\n\t\"teal\",\n\t\"velvet\",\n] as const;\n\nconst NOUNS = [\n\t\"anchor\",\n\t\"arbor\",\n\t\"basin\",\n\t\"beacon\",\n\t\"birch\",\n\t\"canyon\",\n\t\"cedar\",\n\t\"cinder\",\n\t\"cove\",\n\t\"delta\",\n\t\"drift\",\n\t\"ferry\",\n\t\"forge\",\n\t\"harbor\",\n\t\"hollow\",\n\t\"lantern\",\n\t\"meadow\",\n\t\"mesa\",\n\t\"orchard\",\n\t\"pier\",\n\t\"prairie\",\n\t\"quarry\",\n\t\"ridge\",\n\t\"summit\",\n\t\"thicket\",\n\t\"tide\",\n\t\"trellis\",\n\t\"vale\",\n\t\"willow\",\n\t\"windmill\",\n\t\"yarrow\",\n\t\"zenith\",\n] as const;\n\n/**\n * djb2 over the whole string. The same hash the agent palette uses — tiny,\n * stable across runs and machines, and good spread for identifier-like input.\n *\n * Hashing the *whole* id matters here. Session ids are uuidv7, which is\n * time-ordered: two sessions started in the same minute share their leading\n * characters, so a hash over a prefix would drop them into the same slug and the\n * same colour — exactly the sessions a person most needs to tell apart.\n */\nfunction djb2(value: string): number {\n\tlet hash = 5381;\n\tfor (let i = 0; i < value.length; i++) {\n\t\thash = ((hash << 5) + hash + value.charCodeAt(i)) >>> 0;\n\t}\n\treturn hash;\n}\n\n/**\n * A second hash over the same string, seeded differently (sdbm). The slug needs\n * two independent indices; running djb2 twice would tie the noun to the\n * adjective and collapse the vocabulary from 1024 pairs to 32.\n */\nfunction sdbm(value: string): number {\n\tlet hash = 0;\n\tfor (let i = 0; i < value.length; i++) {\n\t\thash = (value.charCodeAt(i) + (hash << 6) + (hash << 16) - hash) >>> 0;\n\t}\n\treturn hash;\n}\n\n/** The auto-assigned name for a session: a memorable two-word slug, e.g. `amber-harbor`. */\nexport function sessionSlugFor(sessionId: string): string {\n\tconst adjective = ADJECTIVES[djb2(sessionId) % ADJECTIVES.length];\n\tconst noun = NOUNS[sdbm(sessionId) % NOUNS.length];\n\treturn `${adjective}-${noun}`;\n}\n\n/** The auto-assigned colour slot for a session, in `1…SESSION_COLOR_SLOTS`. */\nexport function sessionColorSlotFor(sessionId: string): number {\n\treturn (djb2(sessionId) % SESSION_COLOR_SLOTS) + 1;\n}\n\n/** Whether `slot` is one a session can actually be assigned. */\nexport function isSessionColorSlot(slot: number): boolean {\n\treturn Number.isInteger(slot) && slot >= 1 && slot <= SESSION_COLOR_SLOTS;\n}\n\n/**\n * The name each slot answers to, and the other spellings that reach it.\n *\n * A slot is a palette position, not a fixed hue — every theme fills\n * `agent1…agent6` with its own colours — but across the built-in themes each\n * position keeps a recognisable family, and that is what these names describe:\n * slot 3 is the warm yellow/amber one wherever you are, slot 6 the blue. That\n * is enough for the thing names are for, which is typing `/color green`\n * instead of remembering that green is the fifth swatch.\n *\n * The aliases are deliberately generous, because a name only helps if the\n * obvious spelling of it works. Each name's initial stands for it, and the\n * hues that sit between slots (`orange`, `red`) resolve to the nearest slot\n * rather than being rejected — the palette has six entries, not a full colour\n * wheel, and refusing `/color red` because the swatch is officially magenta\n * would be pedantry. A theme whose palette drifts from these names is still\n * addressable by number, which is why the numbers never go away.\n */\nconst SESSION_COLOR_NAMES: ReadonlyArray<{\n\treadonly slot: number;\n\treadonly name: string;\n\treadonly aliases: readonly string[];\n}> = [\n\t{ slot: 1, name: \"cyan\", aliases: [\"c\", \"teal\", \"aqua\"] },\n\t{ slot: 2, name: \"purple\", aliases: [\"p\", \"violet\"] },\n\t{ slot: 3, name: \"yellow\", aliases: [\"y\", \"amber\", \"gold\", \"orange\", \"o\"] },\n\t{ slot: 4, name: \"magenta\", aliases: [\"m\", \"pink\", \"rose\", \"red\", \"r\"] },\n\t{ slot: 5, name: \"green\", aliases: [\"g\"] },\n\t{ slot: 6, name: \"blue\", aliases: [\"b\"] },\n];\n\n/** The canonical slot names, in slot order. For usage lines and pickers. */\nexport const SESSION_COLOR_NAME_LIST: readonly string[] = SESSION_COLOR_NAMES.map((entry) => entry.name);\n\n/** What to call `slot` when reporting it back, or undefined if it is not a slot. */\nexport function sessionColorName(slot: number): string | undefined {\n\treturn SESSION_COLOR_NAMES.find((entry) => entry.slot === slot)?.name;\n}\n\n/**\n * The slot an argument to `/color` asks for: a number (`4`), a name\n * (`magenta`), or an initial (`m`). Case and surrounding space do not matter.\n * Undefined when it names no slot at all.\n */\nexport function parseSessionColorSlot(input: string): number | undefined {\n\tconst normalized = input.trim().toLowerCase();\n\tif (!normalized) return undefined;\n\tif (/^\\d+$/.test(normalized)) {\n\t\tconst slot = Number(normalized);\n\t\treturn isSessionColorSlot(slot) ? slot : undefined;\n\t}\n\tconst match = SESSION_COLOR_NAMES.find((entry) => entry.name === normalized || entry.aliases.includes(normalized));\n\treturn match?.slot;\n}\n\n/**\n * The slot one step away from `current`, wrapping at both ends.\n *\n * The keyboard cycle needs this rather than a picker because picking a colour is\n * usually a two-second decision made while looking at four terminals: step until\n * the chip stops looking like its neighbour's, then stop. Wrapping means the\n * cycle has no dead end to back out of, and a session sitting on a slot no theme\n * defines — or on nothing at all — starts from the first one rather than\n * refusing to move.\n */\nexport function cycleSessionColorSlot(current: number, direction: \"forward\" | \"backward\"): number {\n\tif (!isSessionColorSlot(current)) return 1;\n\tconst step = direction === \"forward\" ? 1 : SESSION_COLOR_SLOTS - 1;\n\treturn ((current - 1 + step) % SESSION_COLOR_SLOTS) + 1;\n}\n"]}