diff --git a/apps/website/qa/agents-spec-contract.mjs b/apps/website/qa/agents-spec-contract.mjs
index d9eef813e5..2d7aea60d7 100644
--- a/apps/website/qa/agents-spec-contract.mjs
+++ b/apps/website/qa/agents-spec-contract.mjs
@@ -360,7 +360,12 @@ assert.ok(triModule && HUD_VIEWS.includes('tri'), 'TRI (the app inside the game)
assert.equal(triModule.key, 'r', 'TRI opens on r: digits spent, t is TOOLS, p is PROJECT')
assert.ok(triModule.en.hint.includes('(key r)') && triModule.ru.hint.includes('(клавиша r)'), 'TRI names its letter key in both hints')
for (const lang of ['en', 'ru']) assert.ok(triModule[lang].name && triModule[lang].body.length > 40, `tri: ${lang} copy missing`)
-assert.equal(HUD_KEYS.slice(0, HUD_VIEWS.length).join(''), '1234567890tpr', 'the rail keys are 1-9, 0, t, p, r in that order')
+const lanesModule = MODULES.find((m) => m.tab === 'lanes')
+assert.ok(lanesModule && HUD_VIEWS.includes('lanes'), 'LANES (the swarm\'s width) is a module and a view')
+assert.equal(lanesModule.key, 'l', 'LANES opens on l: digits spent, t is TOOLS, p is PROJECT, r is TRI')
+assert.ok(lanesModule.en.hint.includes('(key l)') && lanesModule.ru.hint.includes('(клавиша l)'), 'LANES names its letter key in both hints')
+for (const lang of ['en', 'ru']) assert.ok(lanesModule[lang].name && lanesModule[lang].body.length > 40, `lanes: ${lang} copy missing`)
+assert.equal(HUD_KEYS.slice(0, HUD_VIEWS.length).join(''), '1234567890tprl', 'the rail keys are 1-9, 0, t, p, r, l in that order')
// 6. Translations are connected through .t27 contract specs, never hardcoded.
// Every specs/i18n/*.t27 the corpus carries is in both catalogs' i18n lists
diff --git a/apps/website/scripts/agents-from-specs.mjs b/apps/website/scripts/agents-from-specs.mjs
index ef956c8589..f4dd9d4bb8 100644
--- a/apps/website/scripts/agents-from-specs.mjs
+++ b/apps/website/scripts/agents-from-specs.mjs
@@ -870,7 +870,12 @@ export function buildSpecCatalogs({ skillSpecs, cronSpecs, agentSpecs = [], func
collisions,
// Grouping the navigator shows: tri commands by owning letter (unbound under '-'), MCP servers by repo.
groups: {
- triByAgent: Object.fromEntries([...new Set(triTools.flatMap((x) => (x.agents.length ? x.agents.map((a) => a.letter) : ['-'])))].sort().map((l) => [l, triTools.filter((x) => (l === '-' ? x.agents.length === 0 : x.agents.some((a) => a.letter === l))).map((x) => x.id)])),
+ // '-' is always present, empty when nothing is unbound. It used to appear
+ // only while some command had no agent, so the day every command gained
+ // one the bucket vanished and the contract read undefined where it had
+ // asked for a list. "Nothing is unbound" is a fact worth stating, not an
+ // absence to infer.
+ triByAgent: Object.fromEntries([...new Set(['-', ...triTools.flatMap((x) => x.agents.map((a) => a.letter))])].sort().map((l) => [l, triTools.filter((x) => (l === '-' ? x.agents.length === 0 : x.agents.some((a) => a.letter === l))).map((x) => x.id)])),
mcpByRepo: Object.fromEntries(['gHashTag/t27', 'gHashTag/trinity'].map((r) => [r, mcpTools.filter((x) => x.repo === r).map((x) => x.id)])),
triByRepo: Object.fromEntries(TOOL_REPOS.map((r) => [r, triTools.filter((x) => x.repo === r).map((x) => x.id)])),
},
diff --git a/apps/website/src/components/QueenLanes.css b/apps/website/src/components/QueenLanes.css
new file mode 100644
index 0000000000..a406688d61
--- /dev/null
+++ b/apps/website/src/components/QueenLanes.css
@@ -0,0 +1,155 @@
+/* QueenLanes.css - LANES, the view that says how many bees can work at once and
+ refuses to let utilisation stand in for work. Same panel skin as QueenIntel:
+ colours come from the --hud-* variables Queen.css defines, with the live
+ palette as fallback, and the body is the only scroll owner. */
+
+.queen27-lanes {
+ display: grid;
+ grid-template-rows: auto minmax(0, 1fr);
+ min-height: 0;
+ height: 100%;
+ border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22));
+ background: var(--hud-panel, rgba(2, 8, 6, 0.86));
+ clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
+}
+
+.queen27-lanes-head {
+ padding: 12px 16px;
+ border-bottom: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22));
+}
+
+.queen27-lanes-head h2 {
+ margin: 0;
+ font-size: 0.82rem;
+ letter-spacing: 0.18em;
+ color: var(--hud-accent, #00ff88);
+}
+
+.queen27-lanes-head p {
+ margin: 6px 0 0;
+ font-size: 0.78rem;
+ line-height: 1.5;
+ color: var(--hud-dim, rgba(214, 255, 235, 0.68));
+}
+
+.queen27-lanes-body {
+ min-height: 0;
+ overflow-y: auto;
+ padding: 14px 16px 20px;
+ display: grid;
+ gap: 12px;
+ align-content: start;
+}
+
+/* ---- the four figures ------------------------------------------------- */
+
+.queen27-lanes-figures {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
+ gap: 10px;
+}
+
+.queen27-lanes-figure {
+ margin: 0;
+ padding: 10px 12px;
+ border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22));
+ background: rgba(0, 255, 136, 0.04);
+ display: grid;
+ gap: 2px;
+}
+
+.queen27-lanes-figure figcaption {
+ font-size: 0.64rem;
+ letter-spacing: 0.16em;
+ color: var(--hud-dim, rgba(214, 255, 235, 0.68));
+}
+
+.queen27-lanes-figure b {
+ font-size: 1.6rem;
+ font-weight: 600;
+ line-height: 1.1;
+ color: var(--hud-accent, #00ff88);
+ font-variant-numeric: tabular-nums;
+}
+
+.queen27-lanes-figure small {
+ font-size: 0.66rem;
+ color: var(--hud-dim, rgba(214, 255, 235, 0.68));
+}
+
+.queen27-lanes-figure-wide {
+ grid-column: span 2;
+}
+
+/* ---- cards ------------------------------------------------------------ */
+
+.queen27-lanes-card {
+ padding: 12px 14px;
+ border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22));
+ background: rgba(2, 8, 6, 0.5);
+}
+
+.queen27-lanes-card h3 {
+ margin: 0 0 8px;
+ font-size: 0.72rem;
+ letter-spacing: 0.16em;
+ color: var(--hud-accent, #00ff88);
+}
+
+.queen27-lanes-card h4 {
+ margin: 12px 0 4px;
+ font-size: 0.68rem;
+ letter-spacing: 0.14em;
+ color: var(--hud-accent, #00ff88);
+}
+
+.queen27-lanes-card p {
+ margin: 0 0 6px;
+ font-size: 0.78rem;
+ line-height: 1.55;
+ color: var(--hud-text, rgba(233, 255, 244, 0.9));
+}
+
+/* The alarm skin is earned, not decorative: it appears only when the last
+ dispatch was refused in under a second, which is a spent quota. */
+.queen27-lanes-card-alarm {
+ border-color: rgba(255, 77, 94, 0.55);
+ background: rgba(255, 77, 94, 0.07);
+}
+
+.queen27-lanes-alarm {
+ color: #ff4d5e;
+ font-weight: 600;
+}
+
+.queen27-lanes-warn {
+ color: #ffc24d;
+}
+
+.queen27-lanes-note,
+.queen27-lanes-meta {
+ font-size: 0.72rem;
+ color: var(--hud-dim, rgba(214, 255, 235, 0.68));
+}
+
+.queen27-lanes-meta {
+ font-variant-numeric: tabular-nums;
+}
+
+.queen27-lanes-formula {
+ display: block;
+ margin: 6px 0;
+ padding: 8px 10px;
+ font-size: 0.72rem;
+ line-height: 1.5;
+ overflow-x: auto;
+ border: 1px dashed var(--hud-line, rgba(0, 255, 136, 0.22));
+ color: var(--hud-accent, #00ff88);
+ background: rgba(0, 0, 0, 0.35);
+}
+
+.queen27-lanes-error {
+ margin: 0;
+ font-size: 0.74rem;
+ color: #ff4d5e;
+}
diff --git a/apps/website/src/components/QueenLanes.tsx b/apps/website/src/components/QueenLanes.tsx
new file mode 100644
index 0000000000..f357f71cb9
--- /dev/null
+++ b/apps/website/src/components/QueenLanes.tsx
@@ -0,0 +1,243 @@
+// The Queen's LANES view: how many bees can work at once, why that number is
+// what it is, and how a player raises it.
+//
+// The view exists because on 2026-09-17 the swarm reached 100% utilisation for
+// the first time and produced nothing at all. Ten lanes were busy; every turn
+// ended `refused` in zero seconds, because all five provider keys answered
+// HTTP 429 (z.ai code 1302, "Rate limit reached"). A lane occupied by a refusal
+// looks exactly like a lane doing work, so the headline number lied.
+//
+// Hence the shape of this panel: utilisation is shown, and immediately beside it
+// the evidence for whether it is *work*. The two are never printed as one.
+//
+// Following the contract in queenHud.ts: every number here is a field the public
+// /queen/status endpoint already sends. The ones the swarm knows but does not
+// publish — how many keys are live, who contributed them, what each has spent —
+// are named as missing, with the endpoint that would carry them. A panel that
+// invents them would be the same lie in a different font.
+
+import { useI18n } from '../i18n/context'
+import './QueenLanes.css'
+
+export interface LanesWorkers {
+ capacity: number
+ active: number
+ idle: number
+ utilization: number
+}
+
+export interface LanesDispatch {
+ issue: number
+ dispatchedAt: string
+ finishedAt: string | null
+ outcome: string | null
+}
+
+export interface LanesStatus {
+ workers?: LanesWorkers | null
+ dispatches?: {
+ total: number
+ finished: number
+ running: number
+ unreviewed?: number
+ latest?: LanesDispatch | null
+ } | null
+}
+
+export interface LanesCopy {
+ directive: string
+ directiveBody: string
+}
+
+const COPY = {
+ en: {
+ capacity: 'LANES',
+ capacityHint: 'bees that can work at once',
+ active: 'BUSY',
+ idle: 'FREE',
+ util: 'UTILISATION',
+ lawTitle: 'THE LANE LAW',
+ law: 'A lane is not a setting. Capacity is the number of provider keys that answer, times the lanes each key is allowed to open. The worker ceiling is only a ceiling: it cannot conjure a lane a key does not pay for.',
+ lawFormula: 'capacity = live keys × lanes per key',
+ lawUnknown: 'How many keys are live is not on the public endpoint. This panel will not guess it.',
+ workTitle: 'IS IT WORK?',
+ workLead: 'Utilisation counts occupied lanes. A refused turn occupies one exactly like a working turn, so the two must be read together, never as one number.',
+ lastDispatch: 'last dispatch',
+ instantRefusal: 'Zero-second refusal — the lane was occupied and nothing was attempted. This is what a spent quota looks like from outside.',
+ refusalHint: 'Refusals are cheap and fast, so a starved swarm reports its best utilisation ever.',
+ healthy: 'The last dispatch ran for a measurable time. That is necessary for real work, and not sufficient: only a branch proves it.',
+ pending: 'The last dispatch is still running.',
+ noDispatch: 'No dispatch recorded yet.',
+ unreviewed: 'finished, awaiting review',
+ donateTitle: 'DONATE A LANE',
+ donateLead: 'The currency is quota, not lanes. A key that answers adds both; a lane without quota multiplies zero. This is the one contribution that raises the ceiling for everybody at once.',
+ xpTitle: 'HOW XP IS EARNED',
+ xpRule: 'XP accrues to the owner of the key a turn ran on — but only for a turn that reached a branch. Never per dispatch: a refused turn occupies a lane exactly like a working one, and paying for it would pay best when the swarm is most broken.',
+ neverTitle: 'THIS PAGE NEVER TAKES A KEY',
+ never: 'No field here accepts a provider key, and none ever will. This is a public static page; a secret typed into it is a secret published. Keys are handed over out of band and held by the server alone.',
+ missingTitle: 'NOT WIRED YET',
+ missing: 'The contributor board, the per-key health and the XP ledger need an endpoint that does not exist yet:',
+ missingEndpoint: 'GET /queen/public-lanes → { keys: [{ owner, state: "live" | "cooldown" | "spent", lanes, turnsToBranch }] }',
+ missingWhy: 'Until it answers, this view shows the swarm-wide numbers above and says nothing about who paid for them.',
+ branchTitle: 'WHERE THE WORK GOES',
+ branchLead: 'A finished turn writes a branch inside the container, which deliberately holds no push credential — so nothing reaches GitHub until something outside pushes it. That publishing step is where a day of bee work can sit unseen.',
+ branchGit: 'GitButler is the intended safe path for it: virtual branches let the queue be published and reviewed in slices instead of as one irreversible flood.',
+ },
+ ru: {
+ capacity: 'ПОЛОСЫ',
+ capacityHint: 'пчёл могут работать одновременно',
+ active: 'ЗАНЯТО',
+ idle: 'СВОБОДНО',
+ util: 'ЗАГРУЗКА',
+ lawTitle: 'ЗАКОН ПОЛОСЫ',
+ law: 'Полоса — не настройка. Ёмкость это число отвечающих ключей провайдера, умноженное на число полос, разрешённых одному ключу. Потолок воркеров — только потолок: он не создаст полосу, за которую не платит ключ.',
+ lawFormula: 'ёмкость = живые ключи × полос на ключ',
+ lawUnknown: 'Сколько ключей живо — публичный эндпоинт не сообщает. Эта панель не станет угадывать.',
+ workTitle: 'А ЭТО РАБОТА?',
+ workLead: 'Загрузка считает занятые полосы. Отказной ход занимает полосу ровно так же, как рабочий, поэтому два числа читаются вместе и никогда не сливаются в одно.',
+ lastDispatch: 'последняя выдача',
+ instantRefusal: 'Отказ за ноль секунд — полоса была занята, а попытки не было. Так снаружи выглядит исчерпанная квота.',
+ refusalHint: 'Отказы дёшевы и мгновенны, поэтому голодающий рой показывает лучшую загрузку в своей истории.',
+ healthy: 'Последняя выдача длилась измеримое время. Это необходимо для настоящей работы и недостаточно: доказывает её только ветка.',
+ pending: 'Последняя выдача ещё выполняется.',
+ noDispatch: 'Выдач пока не записано.',
+ unreviewed: 'завершено, ждёт ревью',
+ donateTitle: 'ОТДАТЬ ПОЛОСУ',
+ donateLead: 'Валюта — квота, а не полосы. Отвечающий ключ добавляет и то и другое; полоса без квоты умножает ноль. Это единственный вклад, который поднимает потолок сразу для всех.',
+ xpTitle: 'КАК НАЧИСЛЯЕТСЯ XP',
+ xpRule: 'XP идёт владельцу ключа, на котором прошёл ход, — но только за ход, дошедший до ветки. Никогда за диспатч: отказной ход занимает полосу так же, как рабочий, и плата за него платила бы лучше всего тогда, когда рой сломан сильнее всего.',
+ neverTitle: 'ЭТА СТРАНИЦА НИКОГДА НЕ ПРИНИМАЕТ КЛЮЧ',
+ never: 'Здесь нет поля для ключа провайдера и не будет. Это публичная статическая страница; секрет, введённый в неё, — секрет опубликованный. Ключи передаются вне игры и хранятся только на сервере.',
+ missingTitle: 'ЕЩЁ НЕ ПОДКЛЮЧЕНО',
+ missing: 'Доска вкладчиков, здоровье по каждому ключу и журнал XP требуют эндпоинта, которого пока нет:',
+ missingEndpoint: 'GET /queen/public-lanes → { keys: [{ owner, state: "live" | "cooldown" | "spent", lanes, turnsToBranch }] }',
+ missingWhy: 'Пока он не отвечает, вкладка показывает общие числа роя выше и молчит о том, кто за них заплатил.',
+ branchTitle: 'КУДА УХОДИТ РАБОТА',
+ branchLead: 'Завершённый ход пишет ветку внутри контейнера, который намеренно не держит push-креденшел, — поэтому до GitHub ничего не доходит, пока её не выложит что-то снаружи. Именно на этом шаге день работы пчёл может простоять невидимым.',
+ branchGit: 'GitButler — предполагаемый безопасный путь для этого: виртуальные ветки позволяют публиковать и ревьюить очередь порциями, а не одним необратимым потоком.',
+ },
+} as const
+
+/** Milliseconds a dispatch lasted, or null when it has not finished. */
+const durationMs = (d: LanesDispatch): number | null => {
+ if (!d.finishedAt) return null
+ const ms = Date.parse(d.finishedAt) - Date.parse(d.dispatchedAt)
+ return Number.isFinite(ms) ? ms : null
+}
+
+export function QueenLanes({
+ status,
+ error,
+ c,
+}: {
+ status: LanesStatus | null
+ error: string | null
+ c: LanesCopy
+}) {
+ const { lang } = useI18n()
+ const t = lang === 'ru' ? COPY.ru : COPY.en
+
+ const workers = status?.workers ?? null
+ const dispatches = status?.dispatches ?? null
+ const latest = dispatches?.latest ?? null
+ const ran = latest ? durationMs(latest) : null
+ // A refusal that took no measurable time never reached the provider. That is
+ // the signature of a spent quota, and the only way to tell it apart from a
+ // busy swarm without reading the container's own logs.
+ const instant = latest?.outcome === 'refused' && ran !== null && ran < 1000
+
+ return (
+
+
+
+
+ )
+}
+
+export default QueenLanes
diff --git a/apps/website/src/components/queenHud.ts b/apps/website/src/components/queenHud.ts
index f9a549abd3..12b3218231 100644
--- a/apps/website/src/components/queenHud.ts
+++ b/apps/website/src/components/queenHud.ts
@@ -7,7 +7,7 @@
// derivable from these types, the number does not exist yet and the panel
// must say so rather than invent it.
-export type HudView = "comb" | "specs" | "kanban" | "map" | "factory" | "research" | "skills" | "crons" | "agents" | "functions" | "tools" | "project" | "tri";
+export type HudView = "comb" | "specs" | "kanban" | "map" | "factory" | "research" | "skills" | "crons" | "agents" | "functions" | "tools" | "project" | "tri" | "lanes";
// In command-panel order: the key that opens a view is HUD_KEYS at the same
// position, and `?tab=` accepts exactly these names. Kept identical to
// lib/queenModules (qa/agents-spec-contract.mjs checks the two lists agree), so
@@ -30,12 +30,15 @@ export const HUD_VIEWS: readonly HudView[] = [
// Thirteenth, on the letter r: TRI, the app at app.t27.ai inside the game (the
// digits are spent, t is TOOLS, p is PROJECT).
"tri",
+ // Fourteenth, on the letter l: LANES -- how many bees can work at once, and
+ // why utilisation is not the same question as whether they are working.
+ "lanes",
] as const;
// The keyboard shortcut per view, by position: the digits 1-9, then 0, then
// letters once the digits are spent. The rail prints HUD_KEYS[i] on button i and
// the shell binds exactly these keys; a tenth or eleventh view takes the next
// entry here and nothing else changes.
-export const HUD_KEYS: readonly string[] = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "t", "p", "r"] as const;
+export const HUD_KEYS: readonly string[] = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "t", "p", "r", "l"] as const;
export const hudKeyOf = (view: HudView): string => HUD_KEYS[HUD_VIEWS.indexOf(view)] ?? "";
export type Territory = "held" | "neutral" | "fog";
diff --git a/apps/website/src/lib/queenModules.ts b/apps/website/src/lib/queenModules.ts
index 991da22f83..a321d7cc8a 100644
--- a/apps/website/src/lib/queenModules.ts
+++ b/apps/website/src/lib/queenModules.ts
@@ -210,6 +210,21 @@ export const MODULES = [
body: 'Приложение app.t27.ai внутри игры. Каждый экран — настоящая страница приложения во фрейме: лента, агент, ИИ-конвейер от сценария через голос, фото, липсинк и видео к редактору, профиль и CRM владельца. Экран записан в адресе (?tab=tri&screen=chat, а для одного профиля ещё path=), поэтому ссылка его открывает, а перезагрузка сохраняет. На t27.ai Telegram не даёт своему входу загрузиться внутри чужого сайта, поэтому экраны, которым нужен человек, говорят об этом и ведут в само приложение; лента работает для всех. Пока приложение не разрешит t27.ai показывать себя во фрейме, экран не отвечает и говорит об этом. Открывается буквой r.',
},
},
+ {
+ tab: 'lanes',
+ key: 'l',
+ glyph: '≡',
+ en: {
+ name: 'LANES',
+ hint: 'How many bees can work at once, and whether they are working (key l)',
+ body: 'The swarm\'s width, and the one question the width cannot answer. Capacity is the number of provider keys that answer times the lanes each key may open — a worker ceiling cannot conjure a lane a key does not pay for. Beside the utilisation figure the view prints the evidence for whether it is work at all: a turn refused in under a second never reached the provider, and a starved swarm reports its best utilisation ever, because a refusal occupies a lane exactly like a real turn. It also states how a player widens the swarm — a donated key adds quota, which is the actual currency — and how XP is earned for it: per turn that reached a branch, never per dispatch. No field on this page accepts a provider key and none ever will; it is a public static page, and a secret typed into it is a secret published. The digits are spent, so this view opens on the letter l.',
+ },
+ ru: {
+ name: 'ПОЛОСЫ',
+ hint: 'Сколько пчёл работают одновременно и работают ли вообще (клавиша l)',
+ body: 'Ширина роя и единственный вопрос, на который ширина не отвечает. Ёмкость — это число отвечающих ключей провайдера, умноженное на число полос, разрешённых одному ключу: потолок воркеров не создаст полосу, за которую не платит ключ. Рядом с цифрой загрузки вид печатает свидетельство того, работа ли это вообще: ход, отказанный быстрее секунды, до провайдера не дошёл, а голодающий рой показывает лучшую загрузку в своей истории, потому что отказ занимает полосу так же, как настоящий ход. Здесь же сказано, чем игрок расширяет рой — отданный ключ добавляет квоту, а квота и есть настоящая валюта — и как за это начисляется XP: за ход, дошедший до ветки, и никогда за диспатч. Ни одно поле этой страницы не принимает ключ провайдера и не будет: это публичная статическая страница, а секрет, введённый в неё, — секрет опубликованный. Цифры заняты, поэтому вид открывается буквой l.',
+ },
+ },
] as const
export type QueenModule = (typeof MODULES)[number];
diff --git a/apps/website/src/pages/Queen.tsx b/apps/website/src/pages/Queen.tsx
index 57562336ff..16ea1f2929 100644
--- a/apps/website/src/pages/Queen.tsx
+++ b/apps/website/src/pages/Queen.tsx
@@ -73,6 +73,7 @@ const ENGINE_FLAG =
typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("engine") : null;
import { useI18n } from "../i18n/context";
import { QueenTri } from "../components/QueenTri";
+import { QueenLanes } from "../components/QueenLanes";
import { QueenIdentity } from "../components/QueenIdentity";
import { hashParamsOf, tabAddress } from "../lib/triScreens";
import {
@@ -157,6 +158,14 @@ interface ResearchGraph {
interface QueenStatus {
status: "ok";
+ /** Lane occupancy as the swarm reports it. Occupancy, not throughput: a
+ refused turn holds a lane exactly like a working one (see QueenLanes). */
+ workers?: {
+ capacity: number;
+ active: number;
+ idle: number;
+ utilization: number;
+ } | null;
/** The swarm's own word for its state on the wire (working, idle, …). */
swarmState?: string | null;
scheduler: {
@@ -339,6 +348,11 @@ const COPY = {
projectSources: "sources pinned",
triView: "TRI",
triHint: "The app inside the game: feed, agent, AI generation, profile and CRM (key r)",
+ lanesView: "LANES",
+ lanesHint: "How many bees can work at once, and whether they are working (key l)",
+ lanesDirective: "LANES",
+ lanesDirectiveBody:
+ "Capacity is live provider keys times the lanes each may open. Utilisation counts occupied lanes — a refused turn occupies one too, so it is printed beside the evidence, never instead of it.",
triScreens: "App screens",
triFeed: "Feed",
triAgent: "Agent",
@@ -663,6 +677,11 @@ const COPY = {
projectSources: "источников закреплено",
triView: "TRI",
triHint: "Приложение внутри игры: лента, агент, ИИ-генерация, профиль и CRM (клавиша r)",
+ lanesView: "ПОЛОСЫ",
+ lanesHint: "Сколько пчёл работают одновременно и работают ли вообще (клавиша l)",
+ lanesDirective: "ПОЛОСЫ",
+ lanesDirectiveBody:
+ "Ёмкость — живые ключи провайдера, умноженные на полосы каждого. Загрузка считает занятые полосы, а отказной ход занимает полосу тоже, поэтому она печатается рядом со свидетельством, а не вместо него.",
triScreens: "Экраны приложения",
triFeed: "Лента",
triAgent: "Агент",
@@ -2405,6 +2424,7 @@ export default function Queen({sharedCatalog}:{sharedCatalog?:UniverseAtlas}={})
// Thirteenth, on the letter r (HUD_KEYS[12]; digits spent, t is TOOLS, p is
// PROJECT): TRI, the app at app.t27.ai inside the game, one screen per address.
{ view: "tri" as const, glyph: "△", label: c.triView, hint: c.triHint },
+ { view: "lanes" as const, glyph: "≡", label: c.lanesView, hint: c.lanesHint },
];
const viewLabel =
commandItems.find((item) => item.view === view)?.label ?? c.combView;
@@ -2830,6 +2850,12 @@ export default function Queen({sharedCatalog}:{sharedCatalog?:UniverseAtlas}={})
projectSources: c.projectSources,
}}
/>
+ ) : boardView === "lanes" ? (
+
) : boardView === "tri" ? (