/** * Route → one English sentence about the page the user is on. * * ── THESE STRINGS ARE NOT TRANSLATED, AND MUST NEVER BE ────────────────────── * They are not interface copy: they are sent to the LLM as part of the message, * inside the harness block, and every system prompt around them is in English. * Routing them through `t()` would make an Italian user send "Pagina file" to a * model reading an English prompt — worse than saying nothing. This is exactly * the kind of thing someone "fixes" by mistake six months from now; it is not a * missing translation, it is the design. * * The i18n rule for this feature splits on the reader: labels the *user* reads * (the eye toggle, the chip in the bubble) are translated like any other UI * string; the text the *model* reads lives here, in English. * * Each entry says what the page shows and what can be done on it — one or two * lines. Where `docs/` has a page for the feature, the sentence ends with a * pointer to it, so the assistant can read the real documentation instead of * guessing; the pointed-at file must exist (a pointer to a missing page sends * the agent into a dead end). */ // The label of the route slice. Constant, so the frontend and the eye tooltip // cannot disagree on what to call it. export const ROUTE_LABEL = 'Open page'; // route id → { title, what, doc? } // `title` is the page's name as the user sees it in the menu; `what` is the // sentence; `doc` is a path under the workspace's read-only `docs/` mount. export const ROUTE_DESCRIPTIONS = { home: { title: 'Chat', what: 'the assistant chat, which is also the app\'s home page — the conversation fills the page instead of sitting in a side panel.', }, inbox: { title: 'Inbox', what: 'everything raised by background work and waiting for an answer: approval requests, questions from background agents, and sign-in prompts from connectors.', doc: 'docs/tasks.md', }, dashboard: { title: 'Dashboard', what: 'instance status, LLM usage charts, the pending inbox items and a short guide.', }, tasks: { title: 'Task Manager', what: 'background work: what is running now, recurring (cron) jobs, one-off scheduled runs, and the history of past runs.', doc: 'docs/tasks.md', }, projects: { title: 'Projects', what: 'the projects this user is a member of — shared workspaces, each with its own folder, chat and member list; a project can be opened, created or shared from here.', doc: 'docs/projects.md', }, files: { title: 'Files', what: 'the file browser over everything this user can reach: their home, both memory stores, shared folders, projects, skills and docs.', doc: 'docs/files.md', }, models: { title: 'Models', what: 'the admin page for the models the instance uses — language, transcription, text-to-speech and image generation.', }, providers: { title: 'LLM providers', what: 'the admin page for the LLM provider accounts and their endpoints and keys.', }, approval: { title: 'Security', what: 'the admin page for security groups and approval rules — which tools an agent may use freely, which need a human to approve them, and which are denied.', }, agents: { title: 'Agents', what: 'the agents installed on this instance: what each one is for, its model and its settings.', doc: 'docs/agents.md', }, users: { title: 'Users', what: 'the admin directory of the people on this instance; each person\'s page holds their profile and what they may use (connectors, plugins, security).', doc: 'docs/access.md', }, roles: { title: 'Roles', what: 'the admin page for roles — the permissions, default assistant and interface mode a group of people gets.', }, 'shared-folders': { title: 'Shared folders', what: 'the admin page for the folders shared across the instance, and who may read or write each one.', doc: 'docs/shared-folders.md', }, connectors: { title: 'Connectors', what: 'the connectors (MCP servers) available here, which ones this user has turned on, and — for an admin — adding or removing them.', doc: 'docs/connectors.md', }, connector: { title: 'Connector detail', what: 'one connector\'s own page: its configuration, its sign-in or pairing state, and a button to test it.', doc: 'docs/connectors.md', }, marketplace: { title: 'Connector marketplace', what: 'the catalogue of connectors that can be installed on this instance, with their versions and updates.', doc: 'docs/connectors.md', }, plugins: { title: 'Plugins', what: 'the admin status board of the installed plugins — one card each, with an enable switch, a health indicator and a link to its settings.', doc: 'docs/access.md', }, 'plugin-detail': { title: 'Plugin detail', what: 'one plugin\'s admin page: its instance-wide settings and a read-only list of who currently has access to it.', doc: 'docs/access.md', }, profile: { title: 'Profile', what: 'this user\'s own account page: display name, avatar, interface language and password.', }, config: { title: 'Config', what: 'the admin page for instance-wide settings, such as the default interface language, the compaction model and debug mode.', doc: 'docs/settings.md', }, 'llm-requests': { title: 'LLM requests', what: 'the debug log of the requests sent to the LLM providers, with the payload of each one.', }, session: { title: 'Conversation detail', what: 'the full record of one conversation, tool calls included.', }, 'system-agents': { title: 'Background agents', what: 'the agents that run on a schedule (event triage, the memory lints, the conversation review): what each does, its settings, and this user\'s own run history.', doc: 'docs/system-agents.md', }, file_viewer: { title: 'File viewer', what: 'one file from the user\'s workspace, opened for reading.', doc: 'docs/files.md', }, tool_detail: { title: 'Tool call detail', what: 'the full record of one tool call: its arguments, its result and how long it took.', }, }; // A plugin-contributed page (`#plugin//`). The route only // carries ids — the page's own title is the plugin's to publish, and the // contributor slice (T6) is what adds it. function describePluginRoute(route) { const m = route.match(/^plugin\/([^/?]+)\/([^/?]+)$/); if (!m) return null; return `Plugin page (#${route}) — a page contributed by the "${m[1]}" plugin (page "${m[2]}").`; } /** * The sentence for a page id, or `null` for an unknown route. * * Shape: `Title (#route) — what it is. More in docs/x.md.` The hash is part of * the sentence on purpose: it is the same string the user sees in the address * bar, so a follow-up question about "this page" and the URL they might paste * refer to the same thing. */ export function describeRoute(page) { if (!page) return null; if (page.startsWith('plugin/')) return describePluginRoute(page); const entry = ROUTE_DESCRIPTIONS[page]; if (!entry) return null; const where = page === 'home' ? 'the home page' : `#${page}`; const doc = entry.doc ? ` More in ${entry.doc}.` : ''; return `${entry.title} (${where}) — ${entry.what}${doc}`; } /** The `route` slice: zero or one item, ready for `setSlice('route', …)`. */ export function routeSliceFor(page) { const value = describeRoute(page); return value ? [{ label: ROUTE_LABEL, value }] : []; } // ── The mobile shell ───────────────────────────────────────────────────────── // `mobile-app.js` routes a fixed set of sections of its own (`#chat`, `#inbox`, // …) and never goes through `pageFromHash`, so the desktop table above cannot // describe them. The shell claims the route slice (`claimRouteProvider` in // `view-context.js`) and renders it from here — same sentence shape, same rule // as the rest of this file: English, and never through t(). const MOBILE_SECTIONS = { chat: { title: 'Chat', what: 'the assistant chat' }, inbox: { title: 'Inbox', what: 'everything raised by background work and waiting for an answer: approval requests, questions from background agents, and sign-in prompts from connectors' }, projects: { title: 'Projects', what: 'the projects this user is a member of; opening one opens its chat' }, notifications:{ title: 'Notifications',what: 'a placeholder section — there is nothing here yet' }, settings: { title: 'Settings', what: 'this user\'s own account page: display name, interface language and password' }, file_viewer: { title: 'File viewer', what: 'one file from the user\'s workspace, opened for reading' }, tool_detail: { title: 'Tool call detail', what: 'the full record of one tool call: its arguments, its result and how long it took' }, }; /** * The mobile route slice, for the shell's current section. `projectId` / * `projectLabel` are set when the chat is bound to a project * (`#chat/project-`); the label arrives asynchronously, so a project chat * may briefly read as the bare id. */ export function mobileRouteSliceFor({ section, projectId, projectLabel } = {}) { const entry = MOBILE_SECTIONS[section]; if (!entry) return []; let where = `#${section}`; let extra = ''; if (section === 'chat' && projectId) { where = `#chat/project-${projectId}`; extra = `, bound to the chat of the project "${projectLabel ?? projectId}"`; } return [{ label: ROUTE_LABEL, value: `${entry.title} (${where}, mobile app) — ${entry.what}${extra}.` }]; }