From add9cb2add85de8bb710aeeae4d6ddb0ccccd3cd Mon Sep 17 00:00:00 2001 From: raiseCatError <315733358+raiseCatError@users.noreply.github.com> Date: Thu, 29 Oct 2026 02:59:48 +0530 Subject: [PATCH] Appearance in plain words: a one-line explanation for every row, simpler group names, no ownership JSON in the review The hub explains what the selected row changes, names the terminal plainly, and drops jargon (configuration snapshot, CLI overrides). The host-appearance review names NMSh's ownership record instead of printing its JSON. --- src/appearance/AppearanceHub.ts | 33 ++++++++++++++++++++++++++------ src/appearance/hostAppearance.ts | 2 ++ tests/appearanceHub.test.ts | 2 +- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/appearance/AppearanceHub.ts b/src/appearance/AppearanceHub.ts index 9dbec638..405fca2f 100644 --- a/src/appearance/AppearanceHub.ts +++ b/src/appearance/AppearanceHub.ts @@ -2,6 +2,7 @@ import {welcomeProvider} from '../output/WelcomeProviders.js'; import type {SurfaceRoute} from '../motion/renderRouting.js'; import type {HostAppearancePlan} from './hostAppearance.js'; import {stripTerminalControls} from '../util/terminalControls.js'; +import {truncateAnsi} from '../util/text.js'; import {chromaPreviewNote} from './chromaNotes.js'; import type {Key} from '../terminal/keys.js'; import type {MotionSettings, PromptConfiguration} from '../prompt/configuration.js'; @@ -61,6 +62,24 @@ const hubRows = (state: AppearanceHubState) => state.kittyShaders !== undefined ? [...BASE_ROWS.slice(0, 3), {id: 'kittyShaders' as const, label: 'Kitty shaders'}, ...BASE_ROWS.slice(3)] : state.ghosttyShaders !== undefined ? [...BASE_ROWS.slice(0, 3), {id: 'glslShaders' as const, label: 'Ghostty shaders'}, ...BASE_ROWS.slice(3)] : BASE_ROWS; +/** One plain sentence per row, shown beside the list: what the row changes, in everyday words. */ +const ROW_HELP: Record = { + 'Theme Studio': 'Colors for NMSh, and optionally for your terminal and other tools.', + Prompt: 'The line shown in front of your commands.', + 'Cursor & effects': 'Cursor shape and color, plus trails and effects when it moves.', + shaders: 'GPU effects your terminal draws itself: cursor trails, glow and shine.', + 'UI chrome': "Colors of NMSh's own panels, borders and highlights.", + Chroma: "Gentle color motion across NMSh's interface.", + Motion: 'Small animations when commands start, finish or fail.', + 'Theme Bridge': 'Share your theme with other command-line tools.', + 'Effects routing': 'Shows whether each effect is drawn by your terminal (GPU) or by NMSh.', + Welcome: 'The greeting and logo shown when NMSh starts.', + Opacity: 'How see-through the window is. Lower it to see your desktop behind the text.', + 'Blur mode': 'Blurs what shows through the window. It needs opacity below 100%.', + Blur: 'How strongly the desktop behind the window is blurred.', + Background: "The window's background color. Inherit keeps your terminal's own color.", +}; + export function createAppearanceHub(hostName: string, host?: AppearanceState, hostGuidance?: string): AppearanceHubState { return {view: 'hub', selected: 0, ...(host ? {host} : {}), hostName, ...(hostGuidance ? {hostGuidance} : {}), hostDirty: false}; } @@ -201,7 +220,7 @@ export function renderAppearanceHub(state: AppearanceHubState, configuration: Pr motion: anyMotion ? `${motion.rendering === 'rich' ? 'Rich' : 'Clean'} · Launch ${MOTION_LABELS[motion.commandLaunch]} · Context ${MOTION_LABELS[motion.contextTransitions]} · Events ${MOTION_LABELS[motion.eventFeedback]}` : 'Off', }; const entries: StudioEntry[] = NMSH_ROWS.map(row => ({ - group: ['theme', 'prompt'].includes(row.id) ? 'Identity' : ['cursor', 'kittyShaders', 'glslShaders'].includes(row.id) ? 'Cursor & host effects' : row.id === 'welcome' ? 'Startup' : 'Interface & motion', + group: ['theme', 'prompt'].includes(row.id) ? 'Look' : ['cursor', 'kittyShaders', 'glslShaders'].includes(row.id) ? 'Cursor & effects' : row.id === 'welcome' ? 'Startup' : 'Interface & motion', label: row.label, value: summaries[row.id], })); if (state.host) { @@ -212,10 +231,11 @@ export function renderAppearanceHub(state: AppearanceHubState, configuration: Pr ]; if (BLUR_MODES[host.blurModeIndex] === 'Numeric') hostRows.push(['Blur', renderValueControl('number', String(host.blurStrength), host.blurStrength / 50)]); hostRows.push(['Background', state.backgroundInput !== undefined ? `RGB: ${state.backgroundInput}_` : stripTerminalControls(host.background ?? 'Inherit host color')]); - entries.push(...hostRows.map(([label, value]) => ({group: `Host window · ${stripTerminalControls(state.hostName)}`, label, value}))); + entries.push(...hostRows.map(([label, value]) => ({group: `Window · ${stripTerminalControls(state.hostName)}`, label, value}))); } const selectedEntry = entries[state.selected]; - return renderStudioLayout({title: 'Appearance', stacked: height === undefined, subtitle: 'Your terminal, composed · select an editor to shape it', + const help = selectedEntry ? ROW_HELP[selectedEntry.label] ?? (/shaders$/u.test(selectedEntry.label) ? ROW_HELP.shaders : undefined) : undefined; + return renderStudioLayout({title: 'Appearance', stacked: height === undefined, subtitle: state.host ? 'Enter opens a row · ←→ changes window values' : 'Enter opens a row', entries, selected: state.selected, // Blur shows through the transparent part of the window only; at 100% opacity there is nothing to blur. notice: [state.hostMessage ?? (state.hostDirty ? 'Unsaved host changes · S reviews, Enter writes and reloads the terminal' : undefined), @@ -224,9 +244,10 @@ export function renderAppearanceHub(state: AppearanceHubState, configuration: Pr ['↑↓', 'select'], ['Enter', 'open'], ...(state.host ? [['←→', 'adjust'], ['S', 'review']] as const : []), ...(state.host || state.canRestoreHost ? [['R', 'restore']] as const : []), ['Esc', 'close']], details: width => [studioLabel(selectedEntry?.label ?? 'Appearance'), - ...studioWrap(selectedEntry?.value ?? '', width), '', studioMuted('Host window · configuration snapshot'), - ...studioWrap(`Host ${stripTerminalControls(state.hostName)} · CLI overrides are not visible`, width).map(studioMuted), - ...studioWrap(state.host ? 'Changes are reviewed before writing, then NMSh asks the terminal to reload so they show at once. R reviews removal of NMSh-owned appearance overrides.' : state.hostGuidance ?? `Opacity and blur are controlled by ${state.hostName}.`, width).map(studioMuted), '', + ...studioWrap(selectedEntry?.value ?? '', width), ...(help ? ['', ...studioWrap(help, width)] : []), '', + studioMuted(truncateAnsi(`Terminal ${stripTerminalControls(state.hostName)}`, width)), + ...studioWrap(state.host ? `Window settings: NMSh shows the exact change before writing, then reloads ${stripTerminalControls(state.hostName)} so it shows at once. R removes NMSh's window settings again.` + : state.hostGuidance ?? `Opacity and blur are controlled by ${stripTerminalControls(state.hostName)}.`, width).map(studioMuted), '', ...studioWrap(`Chroma ${configuration.presentation.preset === 'off' ? 'OFF' : 'ON'} · ${chromaPreviewNote(configuration.presentation.preset !== 'off')}`, width).map(studioMuted)], }, columns, height ?? 72); } diff --git a/src/appearance/hostAppearance.ts b/src/appearance/hostAppearance.ts index 43e1d9b8..9445e59e 100644 --- a/src/appearance/hostAppearance.ts +++ b/src/appearance/hostAppearance.ts @@ -92,6 +92,8 @@ function owned(host: AppearanceHost, context: AppearanceContext) { function preview(changes: readonly FileChange[]): string[] { return changes.filter(change => change.before.content !== change.after).flatMap(change => { + // NMSh's own ownership record is bookkeeping, not a setting: name it instead of printing its JSON. + if (change.before.path.endsWith('.json')) return [`File: ${change.before.path}`, change.after === undefined ? " NMSh's record of what it added is removed." : " NMSh's record of what it added (used to remove it cleanly later)."]; const before = change.before.content ?? ''; const after = change.after ?? ''; // Host includes are append-only. Show only inserted/removed lines, never unrelated secret-bearing config. diff --git a/tests/appearanceHub.test.ts b/tests/appearanceHub.test.ts index 1c451e7c..8439f481 100644 --- a/tests/appearanceHub.test.ts +++ b/tests/appearanceHub.test.ts @@ -13,7 +13,7 @@ test('/appearance always offers the NMSh rows, with or without host integration' for (const label of ['Theme Studio', 'Prompt', 'Cursor & effects', 'UI chrome', 'Chroma', 'Motion', 'Theme Bridge']) assert.match(rendered, new RegExp(label, 'u')); assert.doesNotMatch(rendered, /Prompt & theme/u, 'themes are not presented as prompt-only'); assert.match(rendered, /Theme Bridge\s+Off · every tool Independent/u); - assert.match(rendered, /Host\s+Zed/u); + assert.match(rendered, /Terminal\s+Zed/u); assert.match(rendered, /controlled by Zed/u); assert.doesNotMatch(rendered, /Opacity\s+█/u); });