mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-11 01:39:41 +02:00
fix(cli-registry): codex launch defaults as registry data, ultra footer, schema doc defaults
- Codex footer model detection (c28): the modelDetect.screenLine effort alternation is now built from CODEX_REASONING_EFFORTS plus 'default', so 'ultra' (offered by the codexReasoningEffort App Setting and codex's own /model picker) is read and the launch enum and the footer reader cannot drift again. Still one capture group, 125 characters, no new quantifier. New session-display-model case loops every effort level, ultra included. - No CLI-id branching for launch defaults (c27): the two mode === 'codex' branches the synced codex model/effort defaults added to the create and quick-start routes are replaced by a registry capability, capabilities.launchDefaults (launch param -> settings key, values from a closed enum), declared on the codex entry only. The resolver moved from web/codex-launch-defaults.ts to web/launch-defaults.ts as applyLaunchDefaults(mode, configs, customEndpoint), filling the entry's legacyConfigField object through legacyConfigAliases, still re-validating with SettingsUpdateSchema and never overwriting a caller's value. The route exclusions are unchanged (create: not remote; quick-start: not remote, not Docker, not a custom model endpoint), and quick-start still derives the session model from a bag without ompConfig, as before. schema.ts refuses an undeclared param, an unknown settings key, an empty map, and launchDefaults on an entry with no legacyConfigField. - The no-id-branching guard now carries an exact occurrence count per allowlisted key, so a new copy of an already approved expression fails instead of riding the old approval, with a synthetic anti-vacuity case. - SettingsUpdateSchema JSDoc (c21/c29): 'classic' is the tabArrangement default and 'compact' the headerStatsStyle default, matching the resolvers and the pre-paint script; state/case/ledger are marked opt-in. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
import { z } from 'zod';
|
||||
import { compileVersionRegex, countCaptureGroups, TOKEN_PATTERNS } from './patterns.js';
|
||||
import { isKnownLauncherProfile, isKnownSetenvProfile } from './profiles.js';
|
||||
import type { McpConfigFormat, ModelConfigResolverName } from './types.js';
|
||||
import type { LaunchDefaultSettingKey, McpConfigFormat, ModelConfigResolverName } from './types.js';
|
||||
|
||||
/** A bare CLI id: lowercase, starts with a letter, at most 24 chars. Also used as a CSS/URL token. */
|
||||
const cliId = z
|
||||
@@ -409,6 +409,17 @@ const capabilitiesSchema = z
|
||||
'rejectWords has nothing to filter without a screenLine'
|
||||
)
|
||||
.optional(),
|
||||
// Launch param -> synced App Settings key. The values are a closed enum, like
|
||||
// configResolver: a clis.json override names one of the settings this build knows
|
||||
// how to validate, never an arbitrary key. Params are checked against the declared
|
||||
// ones in the superRefine below.
|
||||
launchDefaults: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.enum(['codexModel', 'codexReasoningEffort'] as const satisfies readonly LaunchDefaultSettingKey[])
|
||||
)
|
||||
.refine((v) => Object.keys(v).length >= 1 && Object.keys(v).length <= 8, 'launchDefaults takes 1 to 8 params')
|
||||
.optional(),
|
||||
privilegedParams: z
|
||||
.array(
|
||||
z
|
||||
@@ -627,6 +638,30 @@ export const CliEntrySchema = z
|
||||
}
|
||||
});
|
||||
|
||||
// Same silent-no-op class again: a launch default for a param the entry never declared
|
||||
// would be filled into the config object and then read by nothing. And without a
|
||||
// `legacyConfigField` the entry's params are read off the request body itself, where a
|
||||
// filled `model` would be a different field (claude's per-session one), so refuse it.
|
||||
const { launchDefaults } = entry.capabilities;
|
||||
if (launchDefaults !== undefined) {
|
||||
if (entry.launch.legacyConfigField === undefined) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'launchDefaults needs launch.legacyConfigField to fill',
|
||||
path: ['capabilities', 'launchDefaults'],
|
||||
});
|
||||
}
|
||||
for (const param of Object.keys(launchDefaults)) {
|
||||
if (!declaredParams.has(param)) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: `launchDefaults param "${param}" is not a declared launch param`,
|
||||
path: ['capabilities', 'launchDefaults', param],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { setenvProfile } = entry.env;
|
||||
if (setenvProfile !== undefined && !isKnownSetenvProfile(setenvProfile)) {
|
||||
ctx.addIssue({
|
||||
|
||||
@@ -671,6 +671,11 @@ const CODEX: CliEntry = {
|
||||
// popup or a bare line of prose does not have that shape. A footer without an effort
|
||||
// word (a model with no reasoning setting) is not read, and the session keeps its
|
||||
// last known or launch model.
|
||||
// The effort words are built from CODEX_REASONING_EFFORTS, the same list the
|
||||
// `reasoningEffort` launch param above admits, plus `default` (what codex prints when
|
||||
// no effort is configured). A hand-kept copy once left out `ultra`, so a session at
|
||||
// that level never named its model. Every word is plain letters, so the join adds no
|
||||
// quantifier and only a few characters to the 200-character compileVersionRegex cap.
|
||||
// ⚠️ It is not always the LAST row. 0.162.0 (measured 2026-10-09) adds a hint row
|
||||
// under it at rest, ` ← for agents · ? for shortcuts` or ` ? for shortcuts`, and
|
||||
// drops it again while a prompt is being typed. With a one-row window the footer was
|
||||
@@ -681,9 +686,13 @@ const CODEX: CliEntry = {
|
||||
// and the `›` composer, and a forged footer-shaped transcript line is not followed by
|
||||
// an indented row, so it is not read.
|
||||
modelDetect: {
|
||||
screenLine: String.raw`(?:^|\n) {2}([A-Za-z0-9][\w.:/@+-]{0,79}) (?:none|minimal|low|medium|high|xhigh|max|default) · [^\n]*(?:\n {2}[^\n]*)?$`,
|
||||
screenLine: String.raw`(?:^|\n) {2}([A-Za-z0-9][\w.:/@+-]{0,79}) (?:${[...CODEX_REASONING_EFFORTS, 'default'].join('|')}) · [^\n]*(?:\n {2}[^\n]*)?$`,
|
||||
screenLines: 2,
|
||||
},
|
||||
// App Settings → Codex model / reasoning effort (synced), filled into a LOCAL launch's
|
||||
// codexConfig wherever the caller left the field unset. Launch-only: nothing writes
|
||||
// codex's own config.toml. Read by applyLaunchDefaults() in src/web/launch-defaults.ts.
|
||||
launchDefaults: { model: 'codexModel', reasoningEffort: 'codexReasoningEffort' },
|
||||
// Two columns, like claude's, measured on a live 0.154.0 answer: the `•`/`›`/`⚠`
|
||||
// markers sit in the gutter, prose continuations sit at 2, and a nested YAML block
|
||||
// the model wrote rendered at 2/4/6/8 for its own 0/2/4/6. Replayed at 100, 120,
|
||||
|
||||
@@ -96,6 +96,14 @@ export type NewlineSequence = 'line-feed' | 'esc-enter';
|
||||
/** The config readers `capabilities.modelDetect.configResolver` may name (src/model-config-resolvers.ts). */
|
||||
export type ModelConfigResolverName = 'deepseek-route';
|
||||
|
||||
/**
|
||||
* The synced App Settings keys `capabilities.launchDefaults` may name (src/web/launch-defaults.ts).
|
||||
* A closed list rather than any settings key, so a clis.json override cannot feed an
|
||||
* arbitrary setting onto a command line; each name must also be a `SettingsUpdateSchema`
|
||||
* key, which the resolver's typing enforces.
|
||||
*/
|
||||
export type LaunchDefaultSettingKey = 'codexModel' | 'codexReasoningEffort';
|
||||
|
||||
/** The MCP config dialects `src/mcp-sync.ts` has an adapter for. */
|
||||
export type McpConfigFormat = 'claude-json' | 'gemini-json' | 'codex-toml' | 'opencode-json' | 'antigravity-json';
|
||||
|
||||
@@ -529,6 +537,20 @@ export interface CliCapabilities {
|
||||
rejectWords?: string[];
|
||||
configResolver?: ModelConfigResolverName;
|
||||
};
|
||||
/**
|
||||
* Synced App Settings that seed this CLI's launch params when the caller left them unset,
|
||||
* keyed by LAUNCH PARAM name (`{ model: 'codexModel' }`), never the legacy wire name; the
|
||||
* resolver translates through `launch.legacyConfigAliases` like every other `param`.
|
||||
*
|
||||
* Filled into the entry's `launch.legacyConfigField` object at create time by
|
||||
* `applyLaunchDefaults()` (src/web/launch-defaults.ts), which re-validates each value
|
||||
* with `SettingsUpdateSchema` and never overwrites a value the caller sent. Which
|
||||
* launches get it is the CALLER's decision (local ones only: never remote, Docker or a
|
||||
* custom model endpoint). `schema.ts` refuses an undeclared param, and an entry without
|
||||
* a `legacyConfigField`, whose params would otherwise be read off the request body itself.
|
||||
* Absent = no launch defaults.
|
||||
*/
|
||||
launchDefaults?: Record<string, LaunchDefaultSettingKey>;
|
||||
/**
|
||||
* Params a non-granted multi-user owner may not set freely, and what they are forced to.
|
||||
* Data-driven so a CUSTOM CLI's bypass flag is clampable exactly like codex's.
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* @fileoverview Launch-time defaults for Codex sessions.
|
||||
*
|
||||
* Resolves the synced App Settings `codexModel` / `codexReasoningEffort` into the
|
||||
* `codexConfig` a launch uses, filling ONLY the fields the caller left unset.
|
||||
* Persisted values are re-validated with `SettingsUpdateSchema`, so a hand-edited
|
||||
* settings.json can never smuggle an unchecked value onto the codex command line.
|
||||
*
|
||||
* Scope is the caller's decision: the create and quick-start routes apply it to
|
||||
* local launches only, never to remote, Docker or custom-endpoint launches.
|
||||
* Nothing here writes Codex's own config files.
|
||||
*/
|
||||
|
||||
import type { CodexConfig } from '../types.js';
|
||||
import { SettingsUpdateSchema } from './schemas.js';
|
||||
import { readJsonConfig, SETTINGS_PATH } from './route-helpers.js';
|
||||
|
||||
/** Resolve launch-only defaults without changing Codex's own configuration files. */
|
||||
export async function resolveCodexLaunchDefaults(
|
||||
config: CodexConfig | undefined,
|
||||
customEndpoint = false
|
||||
): Promise<CodexConfig | undefined> {
|
||||
if (customEndpoint) return config;
|
||||
const settings = await readJsonConfig<Record<string, unknown>>(SETTINGS_PATH, 'Codex launch defaults', {});
|
||||
const model = SettingsUpdateSchema.shape.codexModel.safeParse(settings.codexModel);
|
||||
const effort = SettingsUpdateSchema.shape.codexReasoningEffort.safeParse(settings.codexReasoningEffort);
|
||||
const defaultModel = model.success ? model.data || undefined : undefined;
|
||||
const defaultEffort = effort.success ? effort.data || undefined : undefined;
|
||||
if (!defaultModel && !defaultEffort) return config;
|
||||
return {
|
||||
...config,
|
||||
model: config?.model ?? defaultModel,
|
||||
reasoningEffort: config?.reasoningEffort ?? defaultEffort,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @fileoverview Launch-time defaults from synced App Settings, driven by registry data.
|
||||
*
|
||||
* A CLI entry declares `capabilities.launchDefaults` (launch param -> settings key; today
|
||||
* only codex, `{ model: 'codexModel', reasoningEffort: 'codexReasoningEffort' }`), and
|
||||
* `applyLaunchDefaults()` fills those settings into the entry's `launch.legacyConfigField`
|
||||
* object, setting ONLY the fields the caller left unset. Persisted values are re-validated
|
||||
* with `SettingsUpdateSchema`, so a hand-edited settings.json can never smuggle an
|
||||
* unchecked value onto the command line.
|
||||
*
|
||||
* Scope is the caller's decision: the create and quick-start routes apply it to local
|
||||
* launches only, never to remote, Docker or custom-endpoint launches. Nothing here writes
|
||||
* a CLI's own config files.
|
||||
*/
|
||||
|
||||
import { getCli } from '../config/cli-registry/registry.js';
|
||||
import { SettingsUpdateSchema } from './schemas.js';
|
||||
import { readJsonConfig, SETTINGS_PATH } from './route-helpers.js';
|
||||
|
||||
/**
|
||||
* Return `configs` with the launch defaults of `mode`'s registry entry filled into its
|
||||
* legacy config object (e.g. `codexConfig`). Every other field of `configs` is passed
|
||||
* through untouched, and `configs` itself comes back unchanged (same object) when the entry
|
||||
* declares no defaults, `customEndpoint` is set, or no setting names a value.
|
||||
*/
|
||||
export async function applyLaunchDefaults<T extends object>(
|
||||
mode: string,
|
||||
configs: T,
|
||||
customEndpoint = false
|
||||
): Promise<T> {
|
||||
const entry = getCli(mode);
|
||||
const declared = entry?.capabilities.launchDefaults;
|
||||
const field = entry?.launch.legacyConfigField;
|
||||
if (customEndpoint || !declared || field === undefined) return configs;
|
||||
|
||||
const settings = await readJsonConfig<Record<string, unknown>>(SETTINGS_PATH, 'CLI launch defaults', {});
|
||||
const aliases = entry.launch.legacyConfigAliases ?? {};
|
||||
const current = (configs as Record<string, unknown>)[field] as Record<string, unknown> | undefined;
|
||||
const defaults: Record<string, unknown> = {};
|
||||
for (const [param, settingKey] of Object.entries(declared)) {
|
||||
const parsed = SettingsUpdateSchema.shape[settingKey].safeParse(settings[settingKey]);
|
||||
// '' is the settings' "leave it to the CLI" value, the same as unset.
|
||||
const value = parsed.success ? parsed.data || undefined : undefined;
|
||||
const wireKey = aliases[param] ?? param;
|
||||
if (value !== undefined && (current?.[wireKey] ?? undefined) === undefined) defaults[wireKey] = value;
|
||||
}
|
||||
if (Object.keys(defaults).length === 0) return configs;
|
||||
return { ...configs, [field]: { ...current, ...defaults } };
|
||||
}
|
||||
@@ -116,7 +116,7 @@ import { clampEnvOverridesForOwner } from '../../session-env-clamp.js';
|
||||
import { enabledClis, getCli } from '../../config/cli-registry/registry.js';
|
||||
import type { NewlineSequence } from '../../config/cli-registry/types.js';
|
||||
import { resolveCliLaunchError } from '../../utils/cli-launcher.js';
|
||||
import { resolveCodexLaunchDefaults } from '../codex-launch-defaults.js';
|
||||
import { applyLaunchDefaults } from '../launch-defaults.js';
|
||||
import { legacyConfigForMode } from '../../session-cli-registry-bridge.js';
|
||||
import { isMultiUserMode } from '../../config/multiuser.js';
|
||||
import { AUTH_COOKIE_NAME } from '../middleware/auth.js';
|
||||
@@ -1150,8 +1150,10 @@ export function registerSessionRoutes(
|
||||
const globalNice = await ctx.getGlobalNiceConfig();
|
||||
const modelConfig = await ctx.getModelConfig();
|
||||
const mode = body.mode || 'claude';
|
||||
const launchCodexConfig =
|
||||
mode === 'codex' && !remote ? await resolveCodexLaunchDefaults(body.codexConfig) : body.codexConfig;
|
||||
// Synced App Settings launch defaults (capabilities.launchDefaults, codex's model and
|
||||
// effort today) fill the CLI's own config object where the caller left it unset.
|
||||
// Local launches only: a remote attach runs whatever the remote pane already runs.
|
||||
const launchBody = remote ? body : await applyLaunchDefaults(mode, body);
|
||||
// Where a model override comes from is a capability, and the three answers are
|
||||
// genuinely different mechanisms:
|
||||
// 'flag' — the CLI takes --model, so read the value the caller sent
|
||||
@@ -1166,10 +1168,9 @@ export function registerSessionRoutes(
|
||||
const modelSource = getCli(mode)?.capabilities.model;
|
||||
const model =
|
||||
modelSource?.source === 'flag'
|
||||
? (legacyConfigForMode(mode, { ...body, codexConfig: launchCodexConfig } as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>)?.[modelSource.param ?? 'model'] as string | undefined)
|
||||
? (legacyConfigForMode(mode, launchBody as unknown as Record<string, unknown>)?.[
|
||||
modelSource.param ?? 'model'
|
||||
] as string | undefined)
|
||||
: modelSource?.source === 'claude-settings-file'
|
||||
? body.model || modelConfig?.defaultModel || undefined
|
||||
: undefined;
|
||||
@@ -1186,12 +1187,12 @@ export function registerSessionRoutes(
|
||||
deepSeekConfig: gatedDeepSeekConfig,
|
||||
} = await _clampExternalCliBypassForOwner(
|
||||
owner,
|
||||
launchCodexConfig,
|
||||
body.geminiConfig,
|
||||
body.antigravityConfig,
|
||||
body.piConfig,
|
||||
body.grokConfig,
|
||||
body.deepSeekConfig
|
||||
launchBody.codexConfig,
|
||||
launchBody.geminiConfig,
|
||||
launchBody.antigravityConfig,
|
||||
launchBody.piConfig,
|
||||
launchBody.grokConfig,
|
||||
launchBody.deepSeekConfig
|
||||
);
|
||||
const terminalHistoryConfig = await ctx.getTerminalHistoryConfig();
|
||||
const session = new Session({
|
||||
@@ -1204,14 +1205,14 @@ export function registerSessionRoutes(
|
||||
model,
|
||||
claudeMode: effectiveClaudeMode,
|
||||
allowedTools: claudeModeConfig.allowedTools,
|
||||
openCodeConfig: mode === 'opencode' ? body.openCodeConfig : undefined,
|
||||
openCodeConfig: mode === 'opencode' ? launchBody.openCodeConfig : undefined,
|
||||
codexConfig: mode === 'codex' ? gatedCodexConfig : undefined,
|
||||
geminiConfig: mode === 'gemini' ? gatedGeminiConfig : undefined,
|
||||
antigravityConfig: mode === 'antigravity' ? gatedAntigravityConfig : undefined,
|
||||
piConfig: mode === 'pi' ? gatedPiConfig : undefined,
|
||||
grokConfig: mode === 'grok' ? gatedGrokConfig : undefined,
|
||||
deepSeekConfig: mode === 'deepseek' ? gatedDeepSeekConfig : undefined,
|
||||
ompConfig: resolveOmpConfigForCreate(mode, workingDir, body.ompConfig),
|
||||
ompConfig: resolveOmpConfigForCreate(mode, workingDir, launchBody.ompConfig),
|
||||
resumeSessionId: validatedResumeId,
|
||||
envOverrides: await clampEnvOverridesForOwner(owner, body.envOverrides),
|
||||
effort: body.effort,
|
||||
@@ -3875,23 +3876,31 @@ export function registerSessionRoutes(
|
||||
// Apply global Nice priority config and model config from settings
|
||||
const niceConfig = await ctx.getGlobalNiceConfig();
|
||||
const qsModelConfig = await ctx.getModelConfig();
|
||||
const qsLaunchCodexConfig =
|
||||
mode === 'codex' && !remote && !docker
|
||||
? await resolveCodexLaunchDefaults(codexConfig, !!customModel)
|
||||
: codexConfig;
|
||||
// Synced App Settings launch defaults, as on the create path: local launches only, so
|
||||
// never a remote or Docker case, and never a custom model endpoint launch.
|
||||
const qsRequestConfigs = {
|
||||
openCodeConfig,
|
||||
codexConfig,
|
||||
geminiConfig,
|
||||
antigravityConfig,
|
||||
piConfig,
|
||||
grokConfig,
|
||||
deepSeekConfig,
|
||||
ompConfig,
|
||||
};
|
||||
const qsLaunchConfigs =
|
||||
remote || docker ? qsRequestConfigs : await applyLaunchDefaults(mode, qsRequestConfigs, !!customModel);
|
||||
// ⚠️ The model is read from a bag WITHOUT ompConfig, as it always was here: quick-start
|
||||
// has never taken omp's session model from ompConfig (the create path does). Kept as
|
||||
// found rather than changed in passing.
|
||||
const { ompConfig: qsLaunchOmpConfig, ...qsModelConfigs } = qsLaunchConfigs;
|
||||
// See the create path for why this is a capability rather than a mode ladder.
|
||||
const qsModelSource = getCli(mode)?.capabilities.model;
|
||||
const qsModel =
|
||||
qsModelSource?.source === 'flag'
|
||||
? (legacyConfigForMode(mode, {
|
||||
openCodeConfig,
|
||||
codexConfig: qsLaunchCodexConfig,
|
||||
geminiConfig,
|
||||
antigravityConfig,
|
||||
piConfig,
|
||||
grokConfig,
|
||||
deepSeekConfig,
|
||||
} as unknown as Record<string, unknown>)?.[qsModelSource.param ?? 'model'] as string | undefined)
|
||||
? (legacyConfigForMode(mode, qsModelConfigs as unknown as Record<string, unknown>)?.[
|
||||
qsModelSource.param ?? 'model'
|
||||
] as string | undefined)
|
||||
: qsModelSource?.source === 'claude-settings-file'
|
||||
? qsModelConfig?.defaultModel || undefined
|
||||
: undefined;
|
||||
@@ -3907,16 +3916,16 @@ export function registerSessionRoutes(
|
||||
deepSeekConfig: qsGatedDeepSeekConfig,
|
||||
} = await _clampExternalCliBypassForOwner(
|
||||
owner,
|
||||
qsLaunchCodexConfig,
|
||||
geminiConfig,
|
||||
antigravityConfig,
|
||||
piConfig,
|
||||
grokConfig,
|
||||
deepSeekConfig
|
||||
qsLaunchConfigs.codexConfig,
|
||||
qsLaunchConfigs.geminiConfig,
|
||||
qsLaunchConfigs.antigravityConfig,
|
||||
qsLaunchConfigs.piConfig,
|
||||
qsLaunchConfigs.grokConfig,
|
||||
qsLaunchConfigs.deepSeekConfig
|
||||
);
|
||||
const qsTerminalHistoryConfig = await ctx.getTerminalHistoryConfig();
|
||||
const qsGatedEnvOverrides = await clampEnvOverridesForOwner(owner, envOverrides);
|
||||
const qsResolvedOmpConfig = resolveOmpConfigForCreate(mode, resolvedCasePath, ompConfig);
|
||||
const qsResolvedOmpConfig = resolveOmpConfigForCreate(mode, resolvedCasePath, qsLaunchOmpConfig);
|
||||
|
||||
// Custom Model Endpoint Profiles, applied AT CREATE TIME (docs/custom-model-endpoints-plan.md)
|
||||
// rather than via the dedicated restart-in-place route (POST /api/sessions/:id/custom-
|
||||
@@ -4073,7 +4082,7 @@ export function registerSessionRoutes(
|
||||
claudeMode: qsEffectiveClaudeMode,
|
||||
allowedTools: qsClaudeModeConfig.allowedTools,
|
||||
owner,
|
||||
openCodeConfig: mode === 'opencode' ? openCodeConfig : undefined,
|
||||
openCodeConfig: mode === 'opencode' ? qsLaunchConfigs.openCodeConfig : undefined,
|
||||
codexConfig: mode === 'codex' ? qsGatedCodexConfig : undefined,
|
||||
geminiConfig: mode === 'gemini' ? qsGatedGeminiConfig : undefined,
|
||||
antigravityConfig: mode === 'antigravity' ? qsGatedAntigravityConfig : undefined,
|
||||
|
||||
+7
-6
@@ -1406,14 +1406,14 @@ export const SettingsUpdateSchema = z
|
||||
/**
|
||||
* Tab layout, the arrangement of the tab list (Discussion #426). Display key
|
||||
* (per-device).
|
||||
* 'classic' = one flat list in tab order, as before. The default.
|
||||
* 'state' = a row per state in the header strip (needs you, waiting,
|
||||
* working, idle; option C), sections in the flat side rail and
|
||||
* the sidebar. The default.
|
||||
* the sidebar. Opt-in.
|
||||
* 'case' = one cluster per case (option A): a labelled box in the strip,
|
||||
* a section in the side rail and the sidebar.
|
||||
* a section in the side rail and the sidebar. Opt-in.
|
||||
* 'ledger' = the flat list on an aligned column grid with a status bar
|
||||
* per cell (option B). Header strip on desktop only.
|
||||
* 'classic' = one flat list in tab order, as before.
|
||||
* per cell (option B). Header strip on desktop only. Opt-in.
|
||||
*/
|
||||
tabArrangement: z.enum(['state', 'case', 'ledger', 'classic']).optional(),
|
||||
/**
|
||||
@@ -1457,8 +1457,9 @@ export const SettingsUpdateSchema = z
|
||||
* How the header draws its WS / CPU / MEM / plan-usage cluster. Display key
|
||||
* (per-device), desktop only (the cluster is hidden below 768px).
|
||||
* 'classic' = the bars and the 5H · 7D chip, as before
|
||||
* 'compact' = two pills (WS/CPU/MEM, the plan windows), a ring beside every value
|
||||
* 'tiles' = label over value with a bar underneath, no icons. The default.
|
||||
* 'compact' = two pills (WS/CPU/MEM, the plan windows), a ring beside every value.
|
||||
* The default.
|
||||
* 'tiles' = label over value with a bar underneath, no icons
|
||||
*/
|
||||
headerStatsStyle: z.enum(['classic', 'compact', 'tiles']).optional(),
|
||||
showTokenCount: z.boolean().optional(),
|
||||
|
||||
Reference in New Issue
Block a user