Docs

Settings UI Form Layout — Delivery Notes

Delivery summary for making the Tranquil settings tab read as one coherent form — every setting aligned, new settings aligned for free — and for surfacing a UI-theme picker at the top of the tab. For the decision record, read ADR-0017: One normalized form layout for the Tranquil settings tab. This note says what shipped and where. It builds on ADR-0011 (the tab itself) and the Settings UI & Form Widgets specs.

Status

Implemented and verified in the running app (light + dark). Not yet committed.

PieceState
Control-group DOM normalization (label → description → control)✅ verified
One stacked, edge-aligned layout in both business themes✅ verified
Native <select>appearance: none + custom chevron (edge alignment)✅ verified (probed live)
In-tab UI-theme picker (scoped to tranquil-business-*, live-synced)✅ verified
Bigger/bolder heading, description under heading✅ verified

What shipped & where

tranquil-config/src/index.jscreateTranquilSettingsPanel():

  • Normalize pass. After core’s SettingsPanel renders, iterate .control-group and hoist each .control-label to be the first child, keeping .setting-description inside the label (directly under the title). This flattens core’s per-type markup differences (a mini-editor nests the label inside .controls; a <select> puts it above) into one shape: label (title + description) → controls → buttons.
  • UI-theme picker. A <select> built in the factory, inserted at the top of the panel’s .section-body. Populated from atom.themes.getLoadedThemes() filtered to metadata.theme === 'ui' && name.startsWith('tranquil-business-'). On change it writes core.themes = [uiTheme, activeSyntaxTheme] — preserving the syntax theme exactly like settings-view’s Themes panel. Repopulates on atom.themes.onDidChangeActiveThemes (reflects a switch made elsewhere); that subscription is disposed in the panel’s destroy() alongside the existing Toast-duration config sub.

tranquil-business-{light,dark}/styles/settings.less — a single .settings-view .tranquil-settings block:

  • .control-group is a flex column with explicit order on label (0) / controls (1); the description sits inside the label so it needs no order. Heading 15px/600 @text-primary; description 12px @text-muted directly beneath.
  • Every field — .tranquil-number-input and select.form-control — shares one box: flex: none, box-sizing: border-box, width: 22em, height: 34px, 13px UI font. Left-aligned in a full-width .controls row → left and right edges line up. Trailing buttons (.tranquil-toast-test) follow in the same row at matching height.
  • Flat selects: .settings-view select.form-control gets appearance: none + a custom chevron (@text-secondary SVG data-URI, #%23, same pattern as the checkbox mark), positioned right with padding-right: 30px. The tranquil-scoped rule re-adds that right padding since it sets padding shorthand.

Gotchas worth remembering

  • Native <select> insets its control. With appearance: auto a macOS <select> draws its visible rounded control inside the CSS box and ignores border-radius, so its visible right edge falls short of a sibling <input> at the same box width. The layout boxes were pixel-identical (getBoundingClientRect matched) — only the native rendering differed. Fix is appearance: none + drawing our own chevron. Applies to every settings select, so it’s on the shared rule.
  • Core emits different markup per control type. Don’t assume the label is a sibling of .controls (a mini-editor nests it inside). The normalize pass exists precisely so the theme can style one shape.
  • box-sizing: border-box is load-bearing for edge alignment: the select’s extra right padding for the chevron must not widen its box, or its right edge drifts off the inputs’.
  • Preserve the syntax theme. Writing core.themes from the picker must pass [uiTheme, syntaxTheme]; writing only the UI theme would drop the active syntax theme.
  • Live theme probing. The native-inset cause is invisible in source — it was found by reading computed styles off the running renderer over CDP. That’s a read-only inspection, distinct from the GUI verify workflow.

Related