Skip to content

Commit 73c73ab

Browse files
committed
fix(dashboard): give the settings page the launcher's run-option rules
The settings page rendered the run options as flat, independently checkable boxes while the launcher has real rules between them, so the page could show an option checked that the launcher shows off, and allowed combinations that mean nothing: Eco under Vanilla, Browser on Codex, Auto maintenance without Post-merge cleanup, and anything at all under Transparent. Move the option table and its rules out of Composer into lib/run-option-rows.ts, rendered by both surfaces, so a rule cannot hold in one place and not the other. It is pure data, so the launcher still renders dropdown items and the settings page renders page rows; neither owns the rules. `checked` is the effective value, so nothing claims an option is on while the run ignores it. A disabled row is greyed with its reason rather than hidden: the settings page is where you go to look for a setting, so a vanished row would be worse than a greyed one. The launcher is unchanged (it already hid the Eco drops when Eco is off), and its tests pass untouched. Two smaller cases of the same mistake, fixed here too: - the notification rows now show the delivery capability the bell already did (permission blocked, DISCORD_WEBHOOK / DISCORD_BOT_TOKEN unset) - the spend offset is bounded to the same range as its slider and the sanitizer, which used to clamp a typed 9999 to 50 while the box kept 9999
1 parent 9f748b6 commit 73c73ab

6 files changed

Lines changed: 409 additions & 117 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@gemstack/the-framework": patch
3+
---
4+
5+
Make the settings page obey the same run-option rules as the launcher (#958).
6+
7+
The settings page rendered the run options as flat, independently checkable boxes, while the launcher has real rules between them. So the page could show an option checked that the launcher shows off, and allowed combinations that mean nothing: Eco under Vanilla (nothing left to trim), Browser on Codex (inert, the browser rides Claude Code's MCP config), Auto maintenance without Post-merge cleanup, and anything under Transparent, which overrides the lot.
8+
9+
The table and its rules moved out of the composer into one module both surfaces render, so a rule cannot hold in one place and not the other. A row the rules disable is greyed and shows why, rather than disappearing, since the settings page is where you go to look for a setting. `checked` is now the effective value everywhere, so no surface claims an option is on while the run ignores it.
10+
11+
Two smaller cases of the same thing: the notification rows now show the delivery capability the bell already showed (browser permission blocked, `DISCORD_WEBHOOK` / `DISCORD_BOT_TOKEN` unset), and the spend offset is bounded to the same range as its slider and the sanitizer, instead of accepting a value that was silently clamped on save.

packages/framework-dashboard/components/Composer.tsx

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { AGENTS, AGENT_LABELS, LAUNCHER_PRESETS, type AgentName } from '@gemstac
55
import {
66
usePreferences,
77
updatePreferences,
8-
autopilotEnabled,
98
themePreference,
109
usePreferenceSources,
1110
useProjectFileConfig,
@@ -19,7 +18,8 @@ import { PromptEditor, type PromptEditorHandle } from './PromptEditor.js'
1918
import { PresetCreatePanel } from './PresetCreatePanel.js'
2019
import { PresetsMenu } from './PresetsMenu.js'
2120
import { AgentModelMenu, type AgentOption } from './AgentModelMenu.js'
22-
import { OptionsMenu, type OptionRow, type RunTarget } from './OptionsMenu.js'
21+
import { OptionsMenu, type RunTarget } from './OptionsMenu.js'
22+
import { runOptionRows } from '../lib/run-option-rows.js'
2323
import { AddDeviceDialog } from './AddDeviceDialog.js'
2424
import { useConnectionProfiles, connectLocal, isLoopbackHost, removeProfile, type ConnectionProfile } from '../lib/profiles.js'
2525
import { useSelectedRemoteDeviceId, selectRemoteDevice } from '../lib/remote-target.js'
@@ -143,7 +143,6 @@ export const Composer = forwardRef<ComposerHandle, {
143143
const preferences = usePreferences()
144144
const sources = usePreferenceSources() // #842: which layer won each option
145145
const fileConfig = useProjectFileConfig() // #842: the repo's committed the-framework.yml
146-
const autopilot = autopilotEnabled(preferences)
147146
const technical = preferences.technical ?? false
148147
const vanilla = preferences.vanilla ?? false
149148

@@ -163,16 +162,11 @@ export const Composer = forwardRef<ComposerHandle, {
163162
)
164163
const transparent = preferences.transparent ?? false // #625: the master off-switch (raw Claude Code)
165164
const eco = preferences.eco ?? false
166-
const ecoPlanning = preferences.ecoPlanning ?? false
167-
const ecoResearch = preferences.ecoResearch ?? false
168-
const ecoMaintenance = preferences.ecoMaintenance ?? false
169-
const onBeforeMergeableQuality = preferences.onBeforeMergeableQuality ?? false
170165
const browser = preferences.browser ?? false
171166
const model = preferences.model ?? '' // #628: empty = the driver's default model
172167
const agent = preferences.agent ?? 'claude' // #650: which coding agent drives the run
173168
const target = preferences.target ?? 'local' // #1050: where the run executes (this device / GitHub Actions)
174169
// The stored agent as a display name; an unknown stored value falls back to Claude Code.
175-
const agentLabel = AGENT_LABELS[AGENTS.includes(agent as AgentName) ? (agent as AgentName) : 'claude']
176170
const customPresets = preferences.customPresets ?? [] // #626: the user's own saved prompts
177171
const projectPresets = useProjectPresets() // #1025: presets committed in the open project's repo
178172
const activeProjectId = useActiveProjectId() // #1025: a project to commit a shared preset into
@@ -244,30 +238,10 @@ export const Composer = forwardRef<ComposerHandle, {
244238
onPromptChange?.(value, nextKind)
245239
}
246240

247-
// The Global options as one table (#314). Autopilot's default-on lives in `autopilotEnabled`; Eco
248-
// is disabled + dimmed under Vanilla; the Eco sub-drops show only while Eco is on.
249-
const mainOptions: OptionRow[] = [
250-
// Named for the agent actually selected (#948): under Codex, "Raw Claude Code" was a lie.
251-
{ key: 'transparent', label: 'Transparent', description: `Raw ${agentLabel} — turns the whole framework off.`, title: `Fully transparent (#625): run the agent exactly like plain ${agentLabel}, with no framework system prompt, controls, dashboard, guard, or TODO loop. Overrides the options below.`, checked: transparent },
252-
// Says only what it does (#801): the maintenance stance it used to relax left the system prompt
253-
// with that section (#556), so the countdown is the whole feature.
254-
{ key: 'autopilot', label: 'Autopilot', description: 'Auto-accepts the recommended choice after a countdown.', title: 'Auto-accept the recommended choice after a countdown, instead of waiting for you to pick', checked: autopilot && !transparent, disabled: transparent, disabledReason: 'off while Transparent is on' },
255-
{ key: 'technical', label: 'Technical control', description: 'Surfaces technical detail like tech-stack choices.', title: 'Expose technical detail (e.g. tech-stack choices)', checked: technical && !transparent, disabled: transparent, disabledReason: 'off while Transparent is on' },
256-
{ key: 'vanilla', label: 'Disable system prompt', description: 'Drops the added system prompt; keeps the session controls.', title: "Remove the built-in system prompt but keep the framework's session controls. For a fully raw session, use Transparent. Expand 'Enhanced System Prompt' to read what it removes.", checked: vanilla && !transparent, disabled: transparent, disabledReason: 'off while Transparent is on' },
257-
{ key: 'eco', label: 'Eco', description: 'Trims the system prompt to save tokens.', title: 'Trim the built-in system prompt to save tokens', checked: eco && !ecoDisabled, disabled: ecoDisabled, disabledReason: 'nothing to trim while the system prompt is off' },
258-
{ key: 'onBeforeMergeableQuality', label: 'Post-merge cleanup', description: 'Runs quality passes once it is ready to merge.', title: "When the session signals it's ready for merge, run maintainability, readability, and security-audit passes", checked: onBeforeMergeableQuality && !transparent, disabled: transparent, disabledReason: 'off while Transparent is on' },
259-
// Claude-only (#801): the browser is wired through Claude Code's MCP config, so another agent's
260-
// driver takes no MCP servers and the box would be checkable but inert. The CLI has always
261-
// warned about this (`unguardedNotices`); now the dashboard says it too.
262-
{ key: 'browser', label: 'Browser', description: 'Gives the agent a real browser to inspect pages.', title: 'Give the agent a real browser via chrome-devtools-mcp: navigate pages, read console + network, inspect the DOM, and screenshot', checked: browser && !transparent && agent === 'claude', disabled: transparent || agent !== 'claude', disabledReason: transparent ? 'off while Transparent is on' : 'only on Claude Code — the browser is wired through its MCP config' },
263-
]
264-
const ecoOptions: OptionRow[] = [
265-
{ key: 'ecoPlanning', label: 'Auto planning', description: 'Drops the planning section; the agent plans itself.', title: 'Drop the planning section, letting the agent plan on its own', checked: ecoPlanning },
266-
{ key: 'ecoResearch', label: 'Auto research', description: 'Drops the alternatives/variability section.', title: 'Drop the alternatives/variability section', checked: ecoResearch },
267-
// Gated on Post-merge cleanup (#801): #556 moved the Maintenance section out of the system
268-
// prompt and into the on-before-mergeable prompt, so this trims nothing unless that pass runs.
269-
{ key: 'ecoMaintenance', label: 'Auto maintenance', description: 'Drops the maintenance section from the post-merge prompt.', title: 'Drop the Maintenance section from the post-merge cleanup prompt', checked: ecoMaintenance && onBeforeMergeableQuality, disabled: !onBeforeMergeableQuality, disabledReason: 'only applies while Post-merge cleanup is on' },
270-
]
241+
// The Global options as one table (#314), with every rule between them (#958). The table lives
242+
// in lib/run-option-rows.ts because the settings page renders the same options: a second copy
243+
// would let a rule hold in one place and not the other.
244+
const { main: mainOptions, eco: ecoOptions } = runOptionRows(preferences)
271245

272246
const editorEl = (
273247
<PromptEditor

packages/framework-dashboard/components/OptionsMenu.tsx

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Preferences } from '@gemstack/the-framework'
2+
import type { OptionRow } from '../lib/run-option-rows.js'
23
import { Settings, Check, MonitorSmartphone, Plus, X } from 'lucide-react'
34
import { updatePreferences } from '../lib/preferences.js'
45
import type { ConnectionProfile } from '../lib/profiles.js'
@@ -21,20 +22,9 @@ import {
2122
// checkboxes. Each item writes its preference straight through; the menu stays open so several
2223
// can be flipped at once. Eco's sub-drops appear (indented) when Eco is on.
2324

24-
/** One Global-option row: a preference key plus how its checkbox reads. */
25-
export type OptionRow = {
26-
key: keyof Preferences
27-
label: string
28-
title: string
29-
/** A short one-line summary shown under the label (#654). */
30-
description?: string
31-
checked: boolean
32-
/** Disabled beyond the form-wide busy flag (e.g. Eco has nothing to trim under Vanilla). */
33-
disabled?: boolean
34-
/** Why it's disabled, shown in the description so a greyed row isn't a mystery (the `title`
35-
* tooltip is suppressed on disabled dropdown items). Only rendered while {@link disabled}. */
36-
disabledReason?: string
37-
}
25+
/** One Global-option row. Defined with the rules that build it (#958), and re-exported here so the
26+
* menu's existing importers do not have to care where the table moved to. */
27+
export type { OptionRow } from '../lib/run-option-rows.js'
3828

3929
function setOption(key: keyof Preferences, checked: boolean) {
4030
updatePreferences({ [key]: checked } as Partial<Preferences>)

0 commit comments

Comments
 (0)