Skip to content

One settings surface: App Settings, Session Options and Add Case - #278

Merged
Ark0N merged 9 commits into
masterfrom
appsettings-details
Aug 10, 2026
Merged

One settings surface: App Settings, Session Options and Add Case#278
Ark0N merged 9 commits into
masterfrom
appsettings-details

Conversation

@Ark0N

Copy link
Copy Markdown
Owner

One visual language for the three modals people actually live in, and a
reordering of each around what you open it for.

App Settings

The document side was wider than it needed to be: every row is text on the left
with its control pinned right, so a 960px shell plus a 62ch cap on descriptions
left roughly 350px of nothing between the two. It is now 760x620 with a 176px
rail and descriptions that run most of the way to their switch. Toggle switches
kept their size; only the space around them changed.

Section order follows what you came for:

  1. Updates, carrying only the current version and the update action, which
    is what the modal opens on.
  2. Terminal & Input (Local Echo / zerolag).
  3. Header & Panels, then the rest.
  4. System (paths, automation, remote access) tails the document: set once,
    rarely touched.

Header & Panels gained two things. Every chip now carries the icon of the
button it switches on, so the list reads as the header itself rather than a
column of names, and a live preview sits above them: a scale model of the
app with a header bar, right-docked panels, a toolbar and floating windows,
rebuilt on every chip change so "what does this add" is answered in place,
before saving. The preview owns no icons of its own - it clones .set-chip-ico
out of the chip - so each icon has exactly one copy in index.html and a chip can
never drift from the button it previews.

Session Options and Add Case

Both moved onto the same surface. The set-* rules went from #appSettingsModal
to :is(#appSettingsModal, #sessionOptionsModal, #createCaseModal); an :is()
list takes its most specific argument's specificity and all three are ids, so
every rule kept the weight it had and nothing downstream shifted in the cascade.

What they do NOT share is what the rail means. App Settings stays a table of
contents over one scrolling document; these two really switch, one section
visible and .hidden on the rest, because Summary owns its own scroller,
Respawn is long, and Add Case is six independent forms. Phones get a horizontal
rail strip instead of App Settings' jump pill, which neither of them has.

They also take a size-up App Settings does not: 900px wide, 236px rail, and
height: auto between a 560px floor and 88vh, so the shell is as tall as the
panel showing instead of a fixed box the content rattles in. They are short
panels you act on once, not a document you scan.

Respawn is ordered around what people open it for. Auto-resume on usage
limit is a callout again (accent card, icon, whole card as the hit target): it
is what turns a limit-halted overnight run back on, and as the first row of a
flat list it no longer looked like something you were meant to switch on. Loop
control (status + Enable/Stop) moved above the loop configuration. The three
cycle steps are real checkboxes, not chips - they are numbered steps of one
sequence. The Context tab is renamed Session (label only; data-tab, the
section id and switchOptionsTab('context') are unchanged) with groups
Identity / Context window / Behavior.

Add Case keeps its legacy .form-row markup in all six panels, because
every id in them is read back by session-ui.js and restructuring the forms to
reach the row classes would be a lot of risk for no visual gain. An adapter
block scoped to #createCaseModal .set-doc paints a form row as a row card, its
label as a row label, its .form-hint as a description, and
<details class="advanced-options"> as a collapsed group head.

One trap worth naming: summary { display: flex } drops the browser's own
disclosure triangle, so Clone options, Container settings, Advanced SSH,
Discover existing sessions and Advanced container settings rendered as plain
headings with nothing to say they open. They carry an explicit chevron now,
rotated on [open], with the native marker suppressed in both spellings.

With Add Case moved over, .modal-tabs / .modal-tab-btn / .modal-tab-content
have no users left, so their CSS is deleted from both stylesheets.

Tests and docs

  • test/session-options-structure.test.ts (new): rail-to-section pairing, the
    ids openSessionOptions reads, the one-visible-section invariant, and the
    data-claude-only entries external CLIs drop.
  • test/app-settings-structure.test.ts: rail and document agree on order,
    Updates leads and carries only the version and the updater, every previewed
    chip has both an icon to clone and a slot that exists, and the retired tab
    classes stay gone.
  • test/admin-ui.test.ts: the load-order check matched a bare
    indexOf('session-ui.js'), which the new modal comments shadowed; it matches
    the script tag now.
  • docs/architecture-invariants.md gains a "Settings surface" section, and the
    settings paths that moved are repointed across README and docs.

Verification

Driven in a real browser against a live server on an isolated instance, not just
asserted in jsdom: all sections of all three modals render, rail clicks switch
and reset scroll, chips and the preview react live, Save applies to the real
header and survives a reload, an external-CLI session still loses Respawn and
Ralph and lands on Session, the callout toggles exactly once from both the text
and the switch, the collapsed blocks open from a click on the row, and phone
widths keep the horizontal rail strip. Checked on a light skin too, which is
what caught the preview frame painting itself a grey slab from hardcoded black
alphas (it uses skin tokens now).

npm run test:ci green (4870 passed), plus typecheck, lint, prettier,
check:lockfile, check:frontend-syntax and check:public-assets.

…yout preview
The document side of the settings modal was wider than it needed to be: every
row is text on the left and a switch pinned to the right, so a 960px shell plus
a 62ch cap on the description left a dead gap of ~350px between the two. The
shell is now 840px, the rail 196px, and descriptions run to 78ch, which closes
the gap and makes the right side sit proportionally with the rail.
Section order now leads with what you look at first: System (the version this
install runs and whether an update is waiting, with Updates promoted above
Paths/Automation/Remote access), then Terminal & Input, then Header & Panels.
The modal opens scrolled to System instead of Terminal & Input.
Header & Panels gains two things:
- every chip carries the icon of the button it switches on, so the list reads
as the header itself rather than as a column of names (File Viewer shows the
folder button, Cron the clock, and so on);
- a live preview above the chips: a scale model of the app with a header bar,
right-docked panels, a toolbar and floating windows, rebuilt on every chip
change so "what does this add" is answered in place, before saving.
The preview owns no icons of its own - it CLONES `.set-chip-ico` out of the
chip - so each icon has exactly one copy in index.html and a chip can never
drift from the button it previews. A chip joins the preview by carrying
`data-preview` (which slot) and `data-preview-order` (where in it); readouts
that are not buttons (plan usage, CPU, font size) use `data-preview-text`
instead. The frame is painted from skin tokens only, since hardcoded black
alphas turned it into a grey slab on the four light skins, and it is marked
`data-i18n-skip`: the mock tab names are decoration, and the labels inside are
copies of chip text i18n has already translated.
Cron moved into its own Scheduling group (it is a toolbar button, not a header
one, and the preview places it accordingly).
test/app-settings-structure.test.ts pins the new contract: the rail and the
document agree on order, System leads with the version above the paths, and
every previewed chip has both an icon to clone and a slot that exists.
Session Options was the last modal still wearing the old chrome: a strip of
top tabs over `.form-row` stacks, sitting next to a settings modal that had just
been rebuilt around a rail and grouped row cards. It now uses the same surface.
The `set-*` rules move from `#appSettingsModal` to
`:is(#appSettingsModal, #sessionOptionsModal)`. An `:is()` list takes the
specificity of its most specific argument, and both arguments are ids, so every
rule keeps exactly the weight it had - nothing downstream shifts in the cascade.
What the two modals do NOT share is what the rail means:
- App Settings stays a table of contents over one scrolling document.
- Session Options switches: one `.set-section` visible, `.hidden` on the rest.
Summary owns its own scroller and Respawn is long, so stacking them into a
single document would bury both. `switchOptionsTab` now queries
`.set-rail-item` (it read `.modal-tab-btn` before) and resets the document
scroll, so a switched-to section starts at its own top.
Phones get a horizontal, scrollable rail strip rather than App Settings' sticky
jump pill, which Session Options has no equivalent of. That is close to the tab
bar it replaces, so the phone gesture is unchanged.
Content is regrouped into the row language - label, description, control pinned
right - across all four sections: usage limits / respawn loop / cycle steps /
loop control, identity / token management / this session, tracker / limits, and
the summary timeline. The three cycle-step checkboxes became chips, which is why
`_syncSettingsChips` now covers both modals and Session Options registers one
delegated change listener per page for them.
Every id and handler the JS reads is preserved, and the component classes it
queries (`.duration-preset-btn`, `.duration-custom-input`, `.color-swatch`,
`.respawn-status-text`, `.run-summary-filters .filter-btn`) are untouched.
`data-claude-only` moved onto the rail entries, so external-CLI sessions still
lose Respawn and Ralph and land on Context.
`.modal-tabs`/`.modal-tab-btn`/`.modal-tab-content` now belong to
#createCaseModal alone. test/session-options-structure.test.ts pins the rail to
section pairing, the ids openSessionOptions reads, the one-visible-section
invariant and the Claude-only entries.
The welcome column was 880px tall inside a 752px overlay on a 1470x842
window, so it ran off both ends (title above the top edge, "Or click Run
to start" below the bottom one) with no way to scroll to either.
.welcome-content is now a flex column bounded at the overlay height with
every child fixed except the Resume list, which shrinks and scrolls
internally. Short windows (<=900px tall) get a tighter rhythm as well, so
the list keeps usable height instead of collapsing to two rows.
The open-tabs rail drops its border-right (the gradient already reads as
docked) and widens 19vw -> 25vw, which stays inside the gutter at the
1180px gate (295px of 310px). The status pill moves from beside the name
down to the created/active stamps line, handing the full row width to the
session name: names render whole instead of ellipsizing
"w34-claudeman: mindreading" into "w34-claudeman: ...", and wrap to a
second line only when they still do not fit.
Verified against the live server with the edited files served into the
page: content fits the overlay at 1180x800 through 2560x1440 and on phone
widths, no clipped names or stamps, no page errors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ab chrome
Three things, all on the same surface.
**Tighter.** The shell drops to 760x620 (was 840x700) and the density comes
down with it: rail 176px, doc padding 15px, row padding 5px 10px, group gaps
3px, section head 0.88rem, row label 0.76rem, description 0.645rem. The model
cards were the biggest block in the document and shrink the most (6px 8px
padding, 0.72rem name). The toggle switches keep their size on purpose - only
the space around them was the problem.
**Checkboxes stay checkboxes.** The respawn cycle steps go back to real
checkboxes in a row card (`.set-checks` / `.set-check`) rather than the chips
they briefly became: they are numbered steps of one sequence, not a set of
independent tags, and chips read as the latter.
**Add Case joins the surface.** Same shell, rail and sections; its rail
switches panels like Session Options'. The six panels keep their legacy
`.form-row` markup - every id in them is read back by session-ui.js, so
restructuring the forms would be a lot of risk for no visual gain. Instead an
adapter block scoped to `#createCaseModal .set-doc` maps the old primitives
onto the look: a form row paints as a row card, its label as a row label, its
`.form-hint` as a row description, `<details class="advanced-options">` as a
collapsed group head. `.form-row` everywhere else is untouched.
With that, `.modal-tabs` / `.modal-tab-btn` / `.modal-tab-content` have no
users left, so their CSS is deleted from both stylesheets and the guard in
test/app-settings-structure.test.ts flips from "the settings modal must not
steal these shared classes" to "nothing uses them any more" - a reappearance
now means a modal drifted back off the shared surface.
…esume
The shared surface is tuned for App Settings: a long, dense document you scan.
Add Case and Session Options are the opposite - a handful of short panels you
act on once - and at that density they read as a few small fields marooned in a
large empty frame, with rail entries too small to aim at.
Both now take the same size-up while App Settings stays tight: 900px wide, a
236px rail with 0.9rem entries and 19px icons, 0.88rem row labels, 0.82rem
fields, and `height: auto` between a 560px floor and 88vh - so the shell is as
tall as the panel showing instead of a fixed box the content rattles in
(Summary opened two thirds empty before).
Respawn is reordered around what people come to it for:
- Auto-resume is a CALLOUT again, not the first row of a list. It is what turns
a limit-halted overnight run back on, so it gets an accent card, an icon, and
a hit target covering the whole card (the label wraps its own switch - no
`for`, since nesting already associates them and the pair has historically
double-fired). The armed "resumes at HH:MM" note renders inside it.
- Loop control (status + Enable/Stop) moves ABOVE the loop configuration. A
running loop is the thing you open this tab to see or stop, and Enable is the
point of the tab either way; it was previously below three groups of config.
- Enable/Stop and the status pill scale with the rows around them.
The Context tab is renamed Session, since "context" only described one of its
three groups, and those groups become Identity / Context window / Behavior.
`summary { display: flex }` in the Add Case adapter drops the browser's own
disclosure triangle, so Clone options, Container settings, Advanced SSH,
Discover existing sessions and Advanced container settings rendered as plain
uppercase headings with nothing to say they open. Reported as exactly that.
Each summary now carries an explicit chevron that rotates 180 degrees on
`[open]`, matching the Advanced group in App Settings, plus a hover state on
the row. The default marker is suppressed in both spellings (`list-style` and
`::-webkit-details-marker`) so a browser that would still paint one does not
end up with two.
A docs pass landed in this worktree while the preview was up (a respawn loop on
the throwaway session it was serving), and it is the documentation this work
needed, so it is reviewed and kept rather than thrown away.
- docs/architecture-invariants.md gains a "Settings surface" section: the one
`:is()` scope and why the id-only list preserves specificity, the anatomy,
the two meanings of the rail, the deliberate two sizes, the phone strip, the
Add Case adapter, the flex-summary chevron trap, the Respawn ordering, the
retired tab chrome, and the live preview's clone-the-chip-icon rule.
- Settings paths are repointed everywhere they moved: Display -> Header &
Panels (header buttons, cron, multi-monitor, response viewer, file viewer),
Settings -> App Settings -> System -> Updates, Panels -> Header & Panels ->
Cross-session features (Read My Mind), Display -> Terminal & Input (gesture
control), Claude Model -> Models -> New Claude sessions.
- Stale counts refreshed (route modules, frontend modules, type files, config
files) and the typecheck script named.
- browser-testing-guide gains the three modal ids and the `set-*` selectors.
- The styles.css block comment covers all three modals.
Two claims it got wrong are corrected here: an external-CLI session opens
Session Options on the Session tab (`switchOptionsTab('context')`), not
Summary - measured in the browser - and the Cron toggle lives under Header &
Panels -> Scheduling, with no "Header Displays" step under it any more.
App Settings opened on a System section that mixed the two things worth seeing
immediately (what this install runs, whether a newer release is waiting) with
three groups nobody sets twice (CLAUDE.md template path, default working
directory, image watcher, Cloudflare tunnel).
Split in two. **Updates** is now the first section and carries only the current
version and the update action, so the modal opens on it and the second thing in
reach is Terminal & Input, where Local Echo lives. **System** keeps Paths,
Automation and Remote access and tails the document, last in the rail.
Also fixes the admin-ui load-order test, which broke on this branch: it located
the modules with a bare `indexOf('session-ui.js')`, and the modal markup now
cites those modules in comments well above the script tags, so it was comparing
a comment against a `<script src>`. It matches the script tag itself now.
@Ark0N
Ark0N merged commit 089283e into masterAug 10, 2026
2 checks passed
CreatureSurvive pushed a commit to CreatureSurvive/Codeman that referenced this pull request Aug 17, 2026
Brings in christianhaberl#4 (three commits,
authorship preserved) and adapts it across the 211 commits master gained
since the branch was cut:
- App Settings control re-authored for the set-* surface (PR Ark0N#278): a
set-row in Layout -> Tabs, replacing the old settings-item markup the
branch targeted. i18n description synced.
- Lineage arcs (PR Ark0N#291, post-branch) are SKIPPED in sidebar layout:
computeLineagePath()'s U-bridge geometry hangs from the horizontal
strip's bottom edge and has no meaning against a vertical list. The
lineage strip-scroll listener now also redraws subagent/ultracode
connectors while the sidebar scrolls vertically.
- The desktop home tab rail (post-branch) defers to the sidebar: both dock
the session list flush left, and the rail would render z-ordered under it.
- Active-row reveal unified into _scrollActiveTabIntoView() (Ark0N#257 landed on
master after the branch): sidebar mode branches to scrollIntoView
block:'nearest', and _fullRenderSessionTabs() restores scrollTop alongside
the Ark0N#257 scrollLeft restore so ambient rebuilds cannot yank a mid-scroll
sidebar back to the top.
- Mobile active-tab hoisting the branch guarded against no longer exists on
master (removed by Ark0N#257); kept master's order-stable render.
Verified: typecheck, lint, format:check, check:frontend-syntax,
check:public-assets, PostCSS parse of both merged stylesheets, the 26 new
jsdom tests, the structural guard suites, and the headless-Chromium harness
(scripts/verify-session-sidebar.mts) green across all seven layout states
at 1600/1000/393px against current master.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Ark0N@claude