* fix(ui): update session-switch-resync test to current handleEvent/setSessionTodos signatures
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(build): split Shiki grammars/themes, CodeMirror legacy modes, and @pierre/diffs into on-demand chunks
Merging @shikijs/langs into one manual vendor chunk made the first language
request download every grammar (7.4 MB raw / 1 MB gzip). Letting Rollup split
these packages per dynamically imported module downloads only the languages,
themes, and modes actually used — matching how the worker build already
behaves. @pierre/diffs is split the same way so its pure patch parser (used by
the eager tool renderer) no longer drags the Shiki-importing render stack into
the startup graph.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(ui): restore lazy heavy views and stop eager settings-graph loading
- MainLayout: DiffView/FilesView/GitView/PlanView return to lazyWithChunkRecovery
(they were silently made static in 2031e3b4 while their Suspense wrappers
remained), keeping the CodeMirror and @pierre/diffs stacks out of startup.
- ContextPanel: same lazy treatment for its Diff/Files/Git/Plan/Walkthrough
tabs, with null Suspense fallbacks.
- CommandPalette imported getSettingsNavIcon from SettingsView, statically
pulling the entire settings surface (SkillsPage -> CodeMirrorEditor -> vim
mode, theme registry -> @pierre/diffs) into the eager graph; the helper now
lives in lib/settings/metadata.
- The windowed SettingsWindow mounts only after its first open: rendering the
lazy component closed made React fetch the SettingsView chunk graph at
startup.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(ui): keep @pierre/diffs + Shiki out of the eager chat graph and defer diff worker warmup
- DiffWorkerProvider no longer statically imports @pierre/diffs/worker or the
theme registry, and no longer spawns 3 workers plus a main-thread shared
highlighter during mount. Pools are created on demand through a dynamic
module load, warmed via requestIdleCallback after startup settles, and
useWorkerPool notifies consumers when a pool becomes available.
- ToolPart's rich diff preview moves to lazily loaded ToolPartDiffPreview;
the plain-text patch (PlainDiffFallback) renders while the chunk loads,
mirroring the existing error fallback. Theme registration happens during
render inside the lazy module so PatchDiff never renders unregistered ids.
- ChatInput mounts its lazy ToolOutputDialog only after the first attachment
preview opens instead of fetching the dialog chunk on the draft screen.
- getMarkdownSyntaxVars moves to a pierre-free markdownSyntaxVars module so
eager code-rendering consumers stop importing the registration module.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* perf(web): load ghostty-web and Nerd Fonts on first terminal use
- ghostty-web (638 KB raw JS + WASM VT) is dynamically imported when a
terminal actually mounts; TerminalView stays eagerly importable for the
bottom dock.
- The ~2 MB of CDN Nerd Fonts are no longer preloaded and force-loaded on
every cold start. index.html exposes an idempotent
__openchamberEnsureNerdFonts hook; TerminalViewport requests it on mount
and waits up to 2s so a cached font is in place before the glyph atlas is
built, while a cold CDN fetch never blocks the terminal. Runtimes without
the hook (VS Code, mobile) resolve immediately, matching their existing
fallback-font behavior.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
---------
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
303 lines
8.1 KiB
TypeScript
303 lines
8.1 KiB
TypeScript
import type { SidebarSection } from '@/constants/sidebar';
|
|
import type { IconName } from '@/components/icon/icons';
|
|
|
|
export type SettingsPageSlug =
|
|
| 'home'
|
|
| 'general'
|
|
| 'projects'
|
|
| 'remote-instances'
|
|
| 'providers'
|
|
| 'usage'
|
|
| 'agents'
|
|
| 'behavior'
|
|
| 'commands'
|
|
| 'mcp'
|
|
| 'plugins'
|
|
| 'skills.installed'
|
|
| 'skills.catalog'
|
|
| 'git'
|
|
| 'appearance'
|
|
| 'chat'
|
|
| 'shortcuts'
|
|
| 'sessions'
|
|
| 'magic-prompts'
|
|
| 'snippets'
|
|
| 'notifications'
|
|
| 'voice'
|
|
| 'tunnel'
|
|
| 'about';
|
|
|
|
type SettingsPageGroup =
|
|
| 'general'
|
|
| 'projects'
|
|
| 'opencode'
|
|
| 'content';
|
|
|
|
export interface SettingsRuntimeContext {
|
|
isVSCode: boolean;
|
|
isWeb: boolean;
|
|
isDesktop: boolean;
|
|
isMobile: boolean;
|
|
}
|
|
|
|
export interface SettingsPageMeta {
|
|
slug: SettingsPageSlug;
|
|
title: string;
|
|
group: SettingsPageGroup;
|
|
kind: 'single' | 'split';
|
|
description?: string;
|
|
keywords?: string[];
|
|
isAvailable?: (ctx: SettingsRuntimeContext) => boolean;
|
|
}
|
|
|
|
export const SETTINGS_PAGE_METADATA: readonly SettingsPageMeta[] = [
|
|
{
|
|
slug: 'home',
|
|
title: 'Settings',
|
|
group: 'general',
|
|
kind: 'single',
|
|
description: 'Search and jump to common pages.',
|
|
keywords: ['search', 'settings'],
|
|
},
|
|
{
|
|
slug: 'general',
|
|
title: 'General',
|
|
group: 'general',
|
|
kind: 'single',
|
|
keywords: ['general', 'startup', 'launch at login', 'autostart', 'tray', 'password', 'passkey', 'security', 'privacy', 'telemetry', 'transport', 'network', 'lan', 'binary', 'cli'],
|
|
},
|
|
{
|
|
slug: 'projects',
|
|
title: 'Projects',
|
|
group: 'projects',
|
|
kind: 'split',
|
|
keywords: ['project', 'projects', 'worktree', 'worktrees', 'repo', 'repository', 'directory'],
|
|
},
|
|
{
|
|
slug: 'remote-instances',
|
|
title: 'Remote Instances',
|
|
group: 'projects',
|
|
kind: 'single',
|
|
keywords: ['ssh', 'remote', 'instances', 'tunnels', 'forwarding', 'connection'],
|
|
isAvailable: (ctx) => !ctx.isVSCode,
|
|
},
|
|
{
|
|
slug: 'providers',
|
|
title: 'Providers',
|
|
group: 'opencode',
|
|
kind: 'split',
|
|
keywords: ['provider', 'providers', 'models', 'model', 'api key', 'api keys', 'openai', 'anthropic', 'ollama', 'credentials'],
|
|
},
|
|
{
|
|
slug: 'usage',
|
|
title: 'Usage',
|
|
group: 'general',
|
|
kind: 'split',
|
|
keywords: ['quota', 'billing', 'tokens', 'usage', 'limits'],
|
|
},
|
|
{
|
|
slug: 'agents',
|
|
title: 'Agents',
|
|
group: 'opencode',
|
|
kind: 'split',
|
|
keywords: ['agent', 'agents', 'prompts', 'tools', 'permissions'],
|
|
},
|
|
{
|
|
slug: 'behavior',
|
|
title: 'Behavior',
|
|
group: 'opencode',
|
|
kind: 'single',
|
|
keywords: ['behavior', 'agents.md', 'system prompt', 'global rules', 'instructions', 'override'],
|
|
},
|
|
{
|
|
slug: 'commands',
|
|
title: 'Commands',
|
|
group: 'opencode',
|
|
kind: 'split',
|
|
keywords: ['command', 'commands', 'slash', 'macros', 'automation'],
|
|
},
|
|
{
|
|
slug: 'mcp',
|
|
title: 'MCP',
|
|
group: 'opencode',
|
|
kind: 'split',
|
|
keywords: ['mcp', 'model context protocol', 'servers', 'tools', 'remote', 'stdio'],
|
|
},
|
|
{
|
|
slug: 'plugins',
|
|
title: 'Plugins',
|
|
group: 'opencode',
|
|
kind: 'split',
|
|
keywords: ['plugin', 'plugins', 'extensions', 'addons', 'npm', 'opencode-wakatime'],
|
|
},
|
|
{
|
|
slug: 'skills.installed',
|
|
title: 'Skills',
|
|
group: 'content',
|
|
kind: 'split',
|
|
keywords: ['skill', 'skills', 'instructions', 'install', 'catalog'],
|
|
},
|
|
{
|
|
slug: 'skills.catalog',
|
|
title: 'Skills Catalog',
|
|
group: 'content',
|
|
kind: 'single',
|
|
keywords: ['install', 'catalog', 'external', 'repository', 'skills catalog'],
|
|
},
|
|
{
|
|
slug: 'git',
|
|
title: 'Git',
|
|
group: 'projects',
|
|
kind: 'single',
|
|
keywords: ['git', 'github', 'identity', 'identities', 'ssh', 'profiles', 'credentials', 'keys', 'commit', 'gitmoji', 'oauth', 'prs', 'issues'],
|
|
isAvailable: (ctx) => !ctx.isVSCode,
|
|
},
|
|
{
|
|
slug: 'appearance',
|
|
title: 'Appearance',
|
|
group: 'general',
|
|
kind: 'single',
|
|
keywords: ['theme', 'font', 'spacing', 'padding', 'corner radius', 'radius', 'input bar', 'keyboard', 'viewport', 'mobile', 'terminal', 'pwa', 'install name', 'app shortcuts'],
|
|
},
|
|
{
|
|
slug: 'chat',
|
|
title: 'Chat',
|
|
group: 'general',
|
|
kind: 'single',
|
|
keywords: ['tools', 'diff', 'reasoning', 'dotfiles', 'draft', 'queue', 'output', 'copy', 'image', 'split messages', 'message actions'],
|
|
},
|
|
{
|
|
slug: 'shortcuts',
|
|
title: 'Shortcuts',
|
|
group: 'general',
|
|
kind: 'single',
|
|
keywords: ['keyboard', 'hotkeys', 'shortcuts', 'bindings'],
|
|
isAvailable: (ctx) => !ctx.isVSCode,
|
|
},
|
|
{
|
|
slug: 'sessions',
|
|
title: 'Sessions',
|
|
group: 'general',
|
|
kind: 'single',
|
|
keywords: ['defaults', 'default agent', 'default model', 'retention', 'memory', 'limits', 'zen'],
|
|
},
|
|
{
|
|
slug: 'magic-prompts',
|
|
title: 'Magic Prompts',
|
|
group: 'content',
|
|
kind: 'split',
|
|
keywords: ['prompts', 'templates', 'git', 'github', 'review', 'commit', 'pull request'],
|
|
isAvailable: (ctx) => !ctx.isVSCode,
|
|
},
|
|
{
|
|
slug: 'snippets',
|
|
title: 'Snippets',
|
|
group: 'content',
|
|
kind: 'split',
|
|
keywords: ['prompt', 'templates', 'multi-run', 'strategy', 'approach'],
|
|
},
|
|
|
|
{ slug: 'notifications', title: 'Notifications', group: 'general', kind: 'single', keywords: ['alerts', 'native', 'summary', 'summarization'], },
|
|
{ slug: 'voice', title: 'Voice', group: 'general', kind: 'single', keywords: ['tts', 'speech', 'voice'], isAvailable: (ctx) => !ctx.isVSCode },
|
|
{ slug: 'tunnel', title: 'External Tunnel', group: 'projects', kind: 'single', keywords: ['tunnel', 'external', 'cloudflare', 'qr', 'remote', 'mobile', 'share'], isAvailable: (ctx) => !ctx.isVSCode },
|
|
{ slug: 'about', title: 'About', group: 'general', kind: 'single', keywords: ['about', 'version', 'updates', 'release', 'changelog'], isAvailable: (ctx) => ctx.isMobile && !ctx.isVSCode },
|
|
] as const;
|
|
|
|
const LEGACY_SIDEBAR_SECTION_TO_SETTINGS_SLUG: Record<SidebarSection, SettingsPageSlug> = {
|
|
sessions: 'sessions',
|
|
agents: 'agents',
|
|
commands: 'commands',
|
|
mcp: 'mcp',
|
|
skills: 'skills.installed',
|
|
providers: 'providers',
|
|
usage: 'usage',
|
|
'git-identities': 'git',
|
|
settings: 'home',
|
|
};
|
|
|
|
export function getSettingsPageMeta(slug: string): SettingsPageMeta | null {
|
|
const normalized = slug.trim().toLowerCase();
|
|
return (SETTINGS_PAGE_METADATA as readonly SettingsPageMeta[]).find((page) => page.slug === normalized) ?? null;
|
|
}
|
|
|
|
export function resolveSettingsSlug(value: string | null | undefined): SettingsPageSlug {
|
|
const normalized = (value ?? '').trim().toLowerCase();
|
|
if (!normalized) {
|
|
return 'home';
|
|
}
|
|
|
|
const legacy = (LEGACY_SIDEBAR_SECTION_TO_SETTINGS_SLUG as Record<string, SettingsPageSlug>)[normalized];
|
|
if (legacy) {
|
|
return legacy;
|
|
}
|
|
|
|
const direct = getSettingsPageMeta(normalized);
|
|
if (direct) {
|
|
return direct.slug;
|
|
}
|
|
|
|
return 'home';
|
|
}
|
|
|
|
// Lives here (not in SettingsView) so light consumers such as the command
|
|
// palette can render settings entries without statically importing the whole
|
|
// settings surface into the eager startup graph.
|
|
export function getSettingsNavIcon(slug: SettingsPageSlug): IconName | null {
|
|
switch (slug) {
|
|
case 'general':
|
|
return 'settings-3';
|
|
case 'projects':
|
|
return 'folders';
|
|
case 'remote-instances':
|
|
return 'computer';
|
|
case 'appearance':
|
|
return 'palette';
|
|
case 'chat':
|
|
return 'chat-ai-3';
|
|
case 'magic-prompts':
|
|
return 'ai-generate-2';
|
|
case 'snippets':
|
|
return 'chat-thread';
|
|
case 'notifications':
|
|
return 'notification-3';
|
|
case 'shortcuts':
|
|
return 'command';
|
|
case 'sessions':
|
|
return 'chat-history';
|
|
|
|
case 'providers':
|
|
return 'cloud';
|
|
case 'agents':
|
|
return 'ai-agent';
|
|
case 'behavior':
|
|
return 'brain';
|
|
case 'commands':
|
|
return 'slash-commands-2';
|
|
case 'mcp':
|
|
return null;
|
|
case 'plugins':
|
|
return 'plug-2';
|
|
|
|
case 'skills.installed':
|
|
return 'book-open';
|
|
case 'skills.catalog':
|
|
return 'book';
|
|
|
|
case 'git':
|
|
return 'git-branch';
|
|
|
|
case 'usage':
|
|
return 'bar-chart-2';
|
|
case 'voice':
|
|
return 'mic';
|
|
case 'tunnel':
|
|
return 'home-office';
|
|
case 'about':
|
|
return 'information';
|
|
case 'home':
|
|
return null;
|
|
default:
|
|
return 'robot-2';
|
|
}
|
|
}
|