Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/settings-run-option-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@gemstack/the-framework": patch
---

Make the settings page obey the same run-option rules as the launcher (#958).

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.

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.

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.
38 changes: 6 additions & 32 deletions packages/framework-dashboard/components/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { AGENTS, AGENT_LABELS, LAUNCHER_PRESETS, type AgentName } from '@gemstac
import {
usePreferences,
updatePreferences,
autopilotEnabled,
themePreference,
usePreferenceSources,
useProjectFileConfig,
Expand All @@ -19,7 +18,8 @@ import { PromptEditor, type PromptEditorHandle } from './PromptEditor.js'
import { PresetCreatePanel } from './PresetCreatePanel.js'
import { PresetsMenu } from './PresetsMenu.js'
import { AgentModelMenu, type AgentOption } from './AgentModelMenu.js'
import { OptionsMenu, type OptionRow, type RunTarget } from './OptionsMenu.js'
import { OptionsMenu, type RunTarget } from './OptionsMenu.js'
import { runOptionRows } from '../lib/run-option-rows.js'
import { AddDeviceDialog } from './AddDeviceDialog.js'
import { useConnectionProfiles, connectLocal, isLoopbackHost, removeProfile, type ConnectionProfile } from '../lib/profiles.js'
import { useSelectedRemoteDeviceId, selectRemoteDevice } from '../lib/remote-target.js'
Expand Down Expand Up @@ -143,7 +143,6 @@ export const Composer = forwardRef<ComposerHandle, {
const preferences = usePreferences()
const sources = usePreferenceSources() // #842: which layer won each option
const fileConfig = useProjectFileConfig() // #842: the repo's committed the-framework.yml
const autopilot = autopilotEnabled(preferences)
const technical = preferences.technical ?? false
const vanilla = preferences.vanilla ?? false

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

// The Global options as one table (#314). Autopilot's default-on lives in `autopilotEnabled`; Eco
// is disabled + dimmed under Vanilla; the Eco sub-drops show only while Eco is on.
const mainOptions: OptionRow[] = [
// Named for the agent actually selected (#948): under Codex, "Raw Claude Code" was a lie.
{ 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 },
// Says only what it does (#801): the maintenance stance it used to relax left the system prompt
// with that section (#556), so the countdown is the whole feature.
{ 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' },
{ 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' },
{ 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' },
{ 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' },
{ 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' },
// Claude-only (#801): the browser is wired through Claude Code's MCP config, so another agent's
// driver takes no MCP servers and the box would be checkable but inert. The CLI has always
// warned about this (`unguardedNotices`); now the dashboard says it too.
{ 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' },
]
const ecoOptions: OptionRow[] = [
{ 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 },
{ key: 'ecoResearch', label: 'Auto research', description: 'Drops the alternatives/variability section.', title: 'Drop the alternatives/variability section', checked: ecoResearch },
// Gated on Post-merge cleanup (#801): #556 moved the Maintenance section out of the system
// prompt and into the on-before-mergeable prompt, so this trims nothing unless that pass runs.
{ 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' },
]
// The Global options as one table (#314), with every rule between them (#958). The table lives
// in lib/run-option-rows.ts because the settings page renders the same options: a second copy
// would let a rule hold in one place and not the other.
const { main: mainOptions, eco: ecoOptions } = runOptionRows(preferences)

const editorEl = (
<PromptEditor
Expand Down
18 changes: 4 additions & 14 deletions packages/framework-dashboard/components/OptionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Preferences } from '@gemstack/the-framework'
import type { OptionRow } from '../lib/run-option-rows.js'
import { Settings, Check, MonitorSmartphone, Plus, X } from 'lucide-react'
import { updatePreferences } from '../lib/preferences.js'
import type { ConnectionProfile } from '../lib/profiles.js'
Expand All @@ -21,20 +22,9 @@ import {
// checkboxes. Each item writes its preference straight through; the menu stays open so several
// can be flipped at once. Eco's sub-drops appear (indented) when Eco is on.

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

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