Skip to content

Group settings pages into sections with icons#230

Merged
sproctor merged 1 commit into
remove-gradle-daemon-jvmfrom
settings-nav-groups-icons
Jul 8, 2026
Merged

Group settings pages into sections with icons#230
sproctor merged 1 commit into
remove-gradle-daemon-jvmfrom
settings-nav-groups-icons

Conversation

@sproctor

@sproctor sproctor commented Jul 8, 2026

Copy link
Copy Markdown
Owner

What

Reorganizes the settings navigation:

  • Grouped into sections via a new SettingsGroupGeneral (no header, stands alone), then Appearance (Appearance, Highlights, Names, Alterations), Game (Actions, Macros, Aliases, Variables), and Account (Accounts, Characters).
  • A leading icon for every page (SettingsPage.icon), rendered by both the mobile (Material3 ListItem leadingContent) and desktop (Jewel Image + ColorFilter.tint) navs.
  • Adds a hand-authored group.xml (two-person) drawable for the Characters page, since the existing Material Symbols set had no multi-person glyph.

Icons

Mostly existing Material Symbols. Three were the closest available stand-ins where the set lacks a dedicated glyph: Macrosspace_dashboard, Variablescircle_filled (no keyboard/data icon). Easy to swap later if better ones are added.

Note

Stacked on #229 (base = remove-gradle-daemon-jvm) so the diff stays limited to the settings changes; will retarget to master once #229 merges.

🤖 Generated with Claude Code

Split the flat settings nav into a General item (no header) plus
Appearance, Game, and Account sections via a new SettingsGroup, and
give every page a leading icon (SettingsPage.icon), rendered by both
the mobile (Material3 ListItem) and desktop (Jewel) navs.

Adds a hand-authored group.xml (two-person) drawable for the Characters
page, since the existing Material Symbols set had no multi-person glyph.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sproctor
sproctor merged commit d03549f into remove-gradle-daemon-jvm Jul 8, 2026
1 check passed
sproctor added a commit that referenced this pull request Jul 15, 2026
Split the flat settings nav into a General item (no header) plus
Appearance, Game, and Account sections via a new SettingsGroup, and
give every page a leading icon (SettingsPage.icon), rendered by both
the mobile (Material3 ListItem) and desktop (Jewel) navs.

Adds a hand-authored group.xml (two-person) drawable for the Characters
page, since the existing Material Symbols set had no multi-person glyph.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
sproctor added a commit that referenced this pull request Jul 17, 2026
…st previews, skin-referenced colors (#232)

* Group settings pages into sections with icons (#230)

Split the flat settings nav into a General item (no header) plus
Appearance, Game, and Account sections via a new SettingsGroup, and
give every page a leading icon (SettingsPage.icon), rendered by both
the mobile (Material3 ListItem) and desktop (Jewel) navs.

Adds a hand-authored group.xml (two-person) drawable for the Characters
page, since the existing Material Symbols set had no multi-person glyph.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* Move per-window styling into settings; split Appearance into Presets and Windows

Per-window color/font settings now live in the main Settings dialog instead of a
standalone popup. A window header's "..." / right-click "Window settings" action
opens Settings focused on that window's editor, routed via a GameViewModel
SharedFlow event collected by the app host.

Add a Windows settings section (Shown / configured-not-shown / other-windows
buckets + add-by-id, live show/hide via a WindowSettingsLiveContext) on desktop
(Jewel) and mobile (M3). Split the Appearance page into separate Presets and
Windows nav pages and drop the multi-line preview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add appearance resolve core + style-rendering characterization tests

Introduce the pure appearance model alongside StyleDefinition (not yet wired):
Background (tri-state Fill/None/Unset), StyleLayer (sparse, weight-not-bold,
per-item font), ResolvedStyle, resolve(), and StyleDefinition<->layer adapters.
toLayer() maps "off" booleans to null so first-set-wins reproduces the old
boolean-OR merge for existing data.

Add golden characterization tests pinning toSpanStyle/toAnnotatedString output
and preset/window/name TOML round-trips, as the safety net for the upcoming
renderer migration onto resolve().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add per-item font + weight fields to style configs (backward-compatible)

PresetStyleConfig/HighlightStyleConfig/NameConfig gain nullable weight,
fontFamily, and fontSize; WindowStyleConfig gains bold/italic/underline for the
shared style editor. All additive with defaults, so pre-existing TOML still
parses and a bold-only preset stays byte-stable (the nullable fields drop when
unset). toStyleDefinition folds an explicit weight back into bold so a hand-set
weight still renders during the transition; per-item font family/size are stored
but not yet applied (the renderer migration wires them next).

Deferred to the UI phase: explicit tri-state None background storage and
applying per-preset skin fonts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Route text rendering through the resolve() model (behavior-preserving)

Replace the StyleDefinition -> SpanStyle choke point with a ResolvedStyle ->
SpanStyle primitive that can carry per-item font family/size/weight and a
tri-state background, and rebuild each leaf's span from resolve() over the leaf's
named-style layers. StyleDefinition.toSpanStyle now delegates through the same
path, so the highlight / name / entire-line / link call sites are unchanged and
every existing render stays byte-identical (the P0 golden tests pass unmodified).

Wiring the preset map to StyleLayer (so per-item preset fonts and tri-state None
actually flow to the renderer) rides with the settings UI in a later phase.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add a global (all-characters) preset layer

PresetRepository gains observeForCharacter (a character's presets layered over the
global ones, character winning per key) and saveGlobal, mirroring the existing
highlight/name global idiom. The renderer's preset resolution becomes
skin -> global -> character. With no global presets saved, behavior is identical
to before.

Window styling globalization rides with the windows scope UI in the next phase
(its geometry stays per-character in SQLite, so only the styling half globalizes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* P5 step 1: source-tracking resolve + config<->StyleLayer mappers

Add the editor-side model: StyleScope, Sourced<T>, SourcedStyle, and
resolveSourced() (the source-tracking twin of resolve(), for "set here /
inherited from …" tags + per-attribute reset). Add tri-state Background <->
WarlockColor helpers (transparent argb 0 == None) and the preset
config<->StyleLayer mappers, with reverse canonicalization (weight 700 -> bold,
no weight field) so bold-only TOML stays byte-stable. Not wired yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* P5: store the base text color/style next to defaultFont

CharacterSettingsConfig gains defaultTextColor / defaultBackgroundColor /
defaultItalic / defaultUnderline beside defaultFont / monoFont — the color half of
the former presets["default"] (defaultFont being the font/weight half). Add
CharacterSettingsRepository.observeBaseStyle / saveBaseStyle that assemble and
split a base StyleLayer, plus the config<->StyleLayer base mappers. Not yet wired
into the renderer or the editor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* P5: make the base style not a preset; feed it from a resolved cascade

Remove "default" from the rendered presets map and from WarlockStyle.presets.
Add WindowRegistry.baseStyle: the resolved base ("default text") color + font +
weight + italic/underline, cascaded character -> global -> skin (from
CharacterSettingsConfig, with a legacy "default"-preset fallback so older configs
keep their default color until the P6 destructive migration). Provide it as
LocalBaseStyle, with the base font half via LocalDefaultFont; the ~10 chrome
sites that read presets["default"] now read the resolved base style.

Behavior-preserving: the P0 golden render tests pass unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Widen the render preset map to sparse StyleLayer

The stream renderer keyed presets by dense StyleDefinition, which cannot
carry per-item fonts or the tri-state (Fill/None/Unset) background. Retype
the whole preset render path to Map<String, StyleLayer> so those reach the
renderer:

- WindowRegistry.presets + WindowRegistryImpl (via the new
  PresetRepository.observeLayersForCharacter), ComposeTextStream, and the
  StyledStringHelpers/AnnotatedStringHelpers.markLinks choke points now
  speak StyleLayer; appendStyledStringLeaf drops its per-leaf .toLayer().
- LocalStyleMap becomes Map<String, StyleLayer>; a resolvedStyle(key) helper
  projects a preset back to a dense StyleDefinition for the input-line chrome
  that still consumes the legacy type. Dialog link lookups read the layer's
  nullable textColor directly.
- getEntireLineStyles reads layer.entireLine and projects to StyleDefinition,
  keeping the entire-line/highlight flatten path (still StyleDefinition-based)
  unchanged.

Behavior-preserving: existing presets carry no fonts, so resolve() output
equals the old StyleDefinition span. Golden StyleRenderCharacterization and
ComposeTextStream tests stay green; Android + benchmark source sets updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add the pure style-editor reducer (StyleEdit + applyEdit)

The shared appearance editor needs a pure, testable core: a StyleEdit per
control, StyleLayer.applyEdit to fold an edit into the edited scope's own
layer (concrete color/background sets; boolean sets explicitly so an inherited
true can be overridden to false; Reset/null clears to inherit), SourcedStyle
.sourceOf(attribute) to drive the per-control "set here"/reset state, and
sampleStyle(stack) for the live preview. No Compose dependency; unit-tested
beside StyleResolveTest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add the shared desktop style editor + rebuild the Presets page

The desktop Appearance -> Presets page becomes a scope-aware master-detail:
a Global/character selector over a list of the base "default text" plus the
named presets (each row drawn in its own resolved style), drilling in to a new
shared DesktopTextStyleEditor.

- DesktopTextStyleEditor (desktop/components): one reusable Jewel editor for a
  single style at one scope. Per attribute it shows the control, a "set here"/
  "from Global"/"from skin" tag, and a reset enabled only when the value was set
  at the edit scope; a live sample renders the resolved cascade. Text color,
  tri-state background (fill + None), weight, italic, underline, and per-item
  font (family/size/weight, sharing the one weight field with the Weight row);
  optional entire-line/monospace rows for reuse by other sections. Edits fold
  through StyleLayer.applyEdit and are handed back as a whole layer to persist.
- core: styleEditorModel(characterLayer, globalLayer, skinLayer) assembles the
  cascade for one item (a null character layer = editing the global scope, which
  then omits any character overrides), plus StyleEditorModel. Unit-tested.
- PresetRepository: observeScopeLayers (one scope's own presets as layers) and
  saveLayer (persist a preset as a sparse StyleLayer).

The base "default text" item edits CharacterSettings' base style; presets edit
their own layer; monospace font (not a style layer) keeps its own row. Global
scope writes the all-characters layer. Mobile Presets still uses the old view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Bring the mobile Presets page to parity with the shared editor

Mirror the desktop rebuild on Material3: a mobile TextStyleEditor (the M3 twin
of DesktopTextStyleEditor - same attribute rows, source tags, per-attribute
reset, tri-state background, per-item font, live sample) and a scope-aware
master-detail PresetsView (base "default text" + named presets each drawn in
their own resolved style, drilling in to the editor; monospace font kept as its
own row). Both platforms now edit the same StyleLayer scopes via styleEditorModel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Rename Presets page to "Text styles"; lift base + mono font above presets

The page now covers the base text style, the monospace font, and the named
presets, so "Presets" was too narrow: rename the settings page to "Text styles"
(SettingsPage.TextStyles). Move the "Default text" and "Monospace font" rows
above a "Presets" heading on both platforms, so the base style and fonts read
as distinct from the named-preset list rather than sitting inside it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Names: adopt the shared style editor

Replace the Names dialog's hex color text fields with the shared style editor
(color pickers + tri-state background + weight + italic + underline), which also
surfaces the bold/italic/underline that NameConfig already stored but the dialog
never exposed. Names carry no font, so the editor runs with showFont = false;
text + sound stay as-is. A commonMain StyleEditing helper maps NameConfig to/from
the editor's StyleLayer, shared by both platforms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Highlights: adopt the shared style editor per capture group

Replace each highlight group's hex color fields + bold/italic/underline/mono
checkboxes with the shared style editor (color pickers, tri-state background,
weight dropdown, italic/underline, monospace). Group styles are still stored as
StyleDefinition, projected to/from the editor's StyleLayer via the StyleEditing
helper. entire-line / match-partial / ignore-case / sound stay as-is. Drops the
mobile-local ColorTextField (now unused after Names + Highlights moved to pickers).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Windows: adopt the shared style editor + thread weight/italic/underline

The per-window settings row now edits its style with the shared editor instead
of two bare color-picker buttons, so a window can override weight, italic, and
underline (not just colors). Those flags already existed on WindowStyleConfig
but were never surfaced; thread them through WindowSettings, observeWindowSettings,
setStyle, and getStyle so they persist and render (via uiState.style.mergeWith).

The editor gets a single-layer `sourced` (tags read "set here"/"default", since
a window has no cascade) but a two-layer `sample` (window over the character
default) for a realistic preview. Fonts stay as the separate normal/mono pickers
(showFont = false); name filter, revert, show/hide, and the window buckets are
unchanged. Per-window global scope is left for later.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* P6: fold legacy default preset into the base + export the new style fields

Base migration (the correctness piece): when a config loads, fold any lingering
presets["default"] into the character/global base style and drop it, so the base
text editor is authoritative and the orphaned preset is cleaned up. Base values
the user has already set win; the legacy preset only fills what the base leaves
unset. Runs per scope in CharacterConfigStore.load() alongside id generation, and
persists the rewritten presets/settings sections. Tested (fold + drop, persistence,
and base-wins).

Export: per-preset weight/fontFamily/fontSize and per-window bold/italic/underline
were editable but silently dropped on export. Add them to StyleExport /
WindowSettingsExport (all defaulted, so older export files still load and the JSON
stays forward/backward compatible) and wire the preset + window export/import
sites. Round-trip + legacy-load tests added.

Still not exported (pre-existing, separate task): the base style itself and the
default/monospace fonts live in character settings, which the export only carries
as typeahead + script prefix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Export the base style + default/mono fonts

The base ("default text") style and the default/monospace fonts live in the
character settings, which the export never carried, so they were lost on
export/import. Add an optional BaseStyleExport block to CharacterExport
(textColor/backgroundColor/italic/underline + font + monoFont) populated on
export and applied on import.

Backward compatible: the block is nullable and defaults to null, so an export
written before it existed (no `baseStyle` key) still deserializes. Import treats
null as "no base in this file": REPLACE keeps the target character's base style
and fonts (the prior behavior), MERGE changes nothing. A present block wins
wholesale on REPLACE and per-set-attribute on MERGE. Round-trip + legacy-load
tests added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Honest style chips: split identity from data + fix the preview surface

The preset list rendered each item's name in that item's own style on the
settings panel surface, which both lied about legibility (game text sits on the
window background, not the panel) and could make the row itself unreadable. Split
the two jobs:

- New shared StyleChip (commonMain): a fixed-width swatch that composites the
  resolved style over the effective *game window* background, never the panel.
  Two distinct boundaries - an outer panel hairline and, only when the background
  is explicitly set (Fill or None), a luminance-derived inset ring - so a
  background equal to the window color is still visible and unset vs None vs Fill
  stay distinguishable. Fixed sample text ("Aa" / a phrase), never the item name.
- Preset rows (both platforms): the name is now normal, always-legible UI text
  (never its own style); the chip carries the style; a muted trailing label shows
  the tri-state background (no bg / none / #hex). Chips resolve the item over the
  base so unset attributes inherit like in game.
- The shared editor's large preview now uses StyleChip over the real window
  background and shows a WCAG contrast warning with the actual ratio when text vs
  effective background drops below 3:1 - warn, never block.
- core: contrastRatio / relativeLuminance util next to resolve, unit-tested.

Decision (skin-ref vs literal, spec section 6): colors stay literal for now;
ColorValue/SkinRef would ripple through StyleLayer/ResolvedStyle/resolve/storage
and is deferred. Names/highlights/windows list swatches not yet on StyleChip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* List swatches: names + highlights adopt the shared StyleChip

Replace the panel-composited swatches in the Names and Highlights lists (both
platforms) with the shared StyleChip, so those rows preview honestly on the game
window background with the tri-state ring, matching the presets list. The item
name/pattern was already legible in these lists; only the swatch changes. They
have no per-scope base handy, so the chip composites against the default game
background (SAFE_DEFAULT_STYLE) - a fine v1 per the spec. Window list rows are
disclosure headers with no swatch; their editor already shows the chip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Annotated tri-state background picker

Replace the editor's background control (a Fill color button + a None checkbox)
with a dedicated picker that presents the three states as explicit, selectable
rows: Inherit, None, Color. Inherit is annotated with what it falls back to
("inherits the window background" / "inherits none" / "inherits #hex") so it is
distinguishable from None, which looks identical in game but does not pick up a
later global background. Color opens the standard color picker. The row now shows
the current state's label (no bg / none / #hex) and opens the dialog.

The presets pages compute the true fallback (resolve of the layers below the
edited scope) and pass it; the flat editors (names/highlights/windows) default to
"inherits the window background", which is exactly what they do. New
DesktopBackgroundPickerDialog (Jewel) + BackgroundPickerDialog (M3) twin; the
inherit annotation helper is shared in commonMain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Skin-ref colors: core ColorValue + StyleLayer ref metadata

Foundation for palette colors that track the skin (spec section 6). ColorValue =
Literal (frozen) | SkinRef (resolves against a skin palette). StyleLayer carries
textColorRef/backgroundRef beside the resolved color, and resolveRefs(palette)
refreshes the colors from the current skin while keeping the ref, so the render
path keeps reading plain WarlockColor and stays untouched (golden tests green).
The reducer gains SetTextColorRef, and a literal color / reset clears the ref.
Unit-tested. Config storage, the compose resolve boundary, and the palette picker
UI follow in later commits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Skin-ref colors: persist the refs in presets + base config

Store the skin-palette slot beside the color in the config so a ref survives
save/reload: PresetStyleConfig gains textColorRef/backgroundColorRef and
CharacterSettingsConfig (base) gains defaultTextColorRef/defaultBackgroundColorRef,
threaded through the toStyleLayer / toPresetStyleConfig / base mappers. All
nullable with null defaults, so older files still parse and a literal-only preset
stays byte-stable. Round-trip tests added. Names/highlights/windows keep literal
colors for now. Compose resolve boundary + palette picker UI follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Skin-ref colors: palette picker + resolve at the compose boundary

Wire skin-referenced text colors end to end for presets + base. A skin color
palette is derived from the skin's presets (name -> text color, name+Bg ->
background); resolveRefs is applied where the skin is known - WindowRegistryImpl
(so refs render and follow the skin) and the presets pages (so the editor/chip
show the resolved color while keeping the ref).

The text-color control now opens a palette picker: clicking a skin swatch stores
a SkinRef that tracks the skin; "Custom color" opens the standard picker and
stores a frozen literal. The row shows "tracks <slot>" when a ref is active.
DesktopColorRefPickerDialog (Jewel) + ColorRefPickerDialog (M3). Render path
stays WarlockColor-based; golden tests unchanged.

Background-fill refs and the names/highlights/windows sections keep literals for
now (the model + config already carry backgroundRef for a later pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Skin-ref colors: background-fill refs too

Extend skin-referencing to the background fill. The background picker now shows
the skin palette below the Fill/None/Inherit rows: a palette swatch stores a
SetBackgroundRef (tracks the skin), while the custom color picker still stores a
frozen literal. The background row shows "tracks <slot>" when a ref is active.

resolveRefs now resolves a background ref to a Fill of the slot color regardless
of the cached background, so the ref survives a save/reload even when the stored
fill color was dropped to unspecified. Reducer gains SetBackgroundRef (a literal
fill / reset still clears the ref). Unit-tested. Both preset text and background
colors now track the skin end to end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Skin-ref colors: names track the skin

NameConfig gains textColorRef/backgroundColorRef; the NameConfig<->StyleLayer
helpers carry them, so the Names editor's palette picker persists a skin ref. The
name render path in WindowRegistryImpl resolves the ref against the skin palette
(names render color only), and the Names editor + list chip resolve for display -
so a name's palette color follows the skin like presets do. Round-trip tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Skin-ref colors: windows track the skin

WindowStyleConfig + WindowSettings gain textColorRef/backgroundColorRef, threaded
through observeWindowSettings. WindowSettings.toStyleLayer() carries the refs for
the shared editor; the windows editor resolves them for display and persists via
a new setStyleLayer (setStyle still clears refs, so Revert resets them).
getStyle(palette) resolves a ref against the skin, falling back to the last
resolved color when no palette is passed - so the render path is correct without
threading the palette (window colors follow the skin live in settings; at render
they refresh on save). Round-trip tested.

With this, all four Appearance sections (presets, names, highlights*, windows)
carry skin refs. (*highlights: their per-group styles still route through the
StyleDefinition-based Highlight model; left literal-only.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Skin-ref colors: highlights track the skin

Convert the Highlight model's per-group styles from Map<Int, StyleDefinition> to
Map<Int, StyleLayer>, so a highlight color can carry a skin ref. HighlightStyleConfig
gains textColorRef/backgroundColorRef; the config<->model mappers (and the legacy
DB migration + WSL/Wrayth importers) go through StyleLayer. The render path in
WindowRegistryImpl resolves each group's ref against the skin palette and flattens
to the dense StyleDefinition that ViewHighlight/applyStyleAtRange still consume, so
the hot path and its golden tests are unchanged. The highlight editor now stores
StyleLayers directly (dropping the toLayer/toStyleDefinition round-trips) and its
palette picker persists refs; the list chip resolves for display. Round-trip tested.

All four Appearance sections - presets, names, highlights, windows - now carry
skin-referenced colors that follow the skin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix appearance-rewrite review findings

Addresses the confirmed findings from the automated deep review on #232.
The dominant theme was that the new editor and resolve model taught
per-attribute cascade semantics that the persistence layer and several
render paths did not implement yet.

Cascade / persistence:
- Merge presets per attribute (new mergeLayers) across skin -> global ->
  character, instead of replacing a preset's layer wholesale per name.
- Make the tri-state style booleans nullable in the config schema so an
  explicit "off" round-trips instead of decaying to "inherit".
- Persist weight for windows, and wire NameConfig's weight/font fields,
  which were previously dead.
- Carry a color's skin ref through foldLegacyDefaultPreset, and fold at
  every config entry point rather than only on load().
- Clamp font weights to 1..1000 when decoding a section, so hand-edited
  TOML cannot crash composition or swallow stream lines.

Render paths:
- Resolve window skin refs at the registry boundary and thread the skin
  palette to GameViewModel's getStyle() call sites.
- Keep names and highlights sparse end to end so their weight, per-item
  font, and bold/italic/underline actually render.
- Consume italic/underline in the stream row text style.

Export / import:
- Carry skin refs (and weight/font for highlights and names) through
  StyleExport, BaseStyleExport, and WindowSettingsExport.
- Map a legacy presets["default"] into baseStyle when importing an export
  that predates the base-style field.
- Stop mergeBase keeping a stale target ref that outranked an imported
  color.

Editors:
- Drive the background picker's selection from the sparse edit layer, so
  Inherit can show as selected.
- Composite the highlights/names previews and contrast warnings against
  the real resolved base background instead of a hardcoded dark color.
- Compute the Windows page's default text from the real base cascade
  rather than the folded-away presets["default"].

Also fixes the iOS build: toSortedMap() is JVM-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* StyleChip: draw a single border

The chip drew two: an always-on outer hairline bounding it against the
settings panel, and an inset ring drawn only when the background was
explicitly set. The conditional ring made some rows read as double-bordered
and others not.

Keep the hairline, which is present in every state and does the actual
bounding. The explicit-vs-inherited distinction the ring encoded is already
reported textually by backgroundLabel() (no bg / none / hex), so no
information is lost.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Preview each preset with the line it styles in game

Every style editor showed the same hardcoded speech line ("You hear
Lyrena say, ...") no matter what was being edited, so the preview said
nothing about the preset in front of you.

Give each preset the in-game line it actually applies to, restoring the
per-preset lines the pre-rewrite Appearance preview had. echo/error/link
had no line there, so those come from where the client emits them.

The style applies to the fragment it really marks, not the whole line:
bold marks the creature name, speech the "You say" verb. StyleSample
carries prefix/styled/suffix and the chip renders the surrounding text in
the base style. Backgrounds follow the same split - a whole-line style
fills the chip, a fragment style paints behind its fragment only, leaving
the line on the window background as it would be in game.

Windows, names, and highlights style arbitrary text, so they get a pangram
instead: it exercises every letter, which is what makes a font or weight
change legible in a one-line preview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Settings: clearer nav selection, drop redundant title, label Base

Three desktop Appearance/settings tweaks:

- The active nav page tinted with the neutral border color at low alpha,
  which barely showed. Use the accent (focused-outline) color instead so
  the selection reads clearly.
- SettingsListScaffold no longer repeats the page title; the settings-dialog
  nav already shows it. Dropped the param and its eight call-site arguments.
- The Presets page gains a "Base" heading over the base-text and monospace
  rows, matching the "Presets" heading below.

Also simplify the base-text preview to the generic pangram rather than its
own room-description line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to 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.

1 participant