Skip to content

UX: fix-now batch, layout model, Client registry, trigger builder, Light settings - #96

Draft
HarryCordewener wants to merge 50 commits into
mainfrom
claude/project-thread-izbu0c
Draft

HarryCordewener wants to merge 50 commits into
mainfrom
claude/project-thread-izbu0c

Conversation

@HarryCordewener

@HarryCordewener HarryCordewener commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Requested by Grave · project thread

The fix-now batch from the UX audit, plus the first wave of the UX plan.

What changed

Fix-now batch

  • Status row at 80 columns no longer wraps onto a second row. Wrapping took a row off every pane and re-sent NAWS. The encoding cell is dropped first, then the ⌃P hint.
  • Meters no longer vanish below 120 columns. When 82/100 does not fit they show 82% instead.
  • F2 and F4 at 80 columns use a single-column layout (ScreenChrome.TooNarrowForTwo and Single) instead of drawing their editors off the right edge.
  • Header and action bars shorten their text instead of cutting off their keys. Headers (SpreadTitle) shorten from the middle, so the close key stays.
  • /connect <host> [port] [tls] saves a world and dials it. /connect <name> opens a world that is already configured.
  • Window fonts: JetBrains Mono and Symbols Nerd Font Mono are embedded in sharpmuterm-window, with their licences copied to licenses/.

Wave 1

  • Layout model (Core/Layouts):
    • Built-in Roleplay and Combat layouts, plus layouts the player saves (config.json layouts).
    • WorldDefinition.Layout and CharacterDefinition.Layout.
    • LayoutResolver tries the character's layout, then the world's, then the accepted suggestion, then Roleplay.
    • LayoutSuggester suggests Combat when vitals arrive, and Roleplay when MSSP reports a MUSH-family codebase.
    • PanelAvailability says which panels have data yet. LayoutFold turns a layout into the existing PaneNode/SplitNode tree.
    • No schema bump. Nothing in the hosts uses it yet.
  • SharpMUTerm.Client, a new project with no UI dependencies:
    • ContributionRegistry with six kinds: commands, panels, settings sections, status cells, overlays and output decorators.
    • ⌃P, the pointer menu and DispatchCommand now read their commands from the registry. The catalog was checked against the old one across 12,288 states before the old one was deleted.
    • New suite SharpMUTerm.Client.Tests, also added to ci.yml.
  • Trigger builder:
    • A ⌃P and right-click entry, Make a trigger from the selection, builds a rule from the selected text. Numbers, quoted speech and player names that GMCP reported become wildcards.
    • New Trigger.MatchType: contains, starts with, * wildcards, or regex. Regex is the default, so old configs load unchanged.
    • While a pattern is being edited, F2 shows how many recent lines it matches, with three samples. Bounded at 2,000 lines, with a 100 ms timeout.
    • New snapshot view trigger-builder.
  • Accessibility:
    • The ten pane tints were re-picked using the Machado 2009 colour-blindness simulation. The worst pair went from ΔE 0.28 to 2.22 (deuteranopia, Dark theme).
    • The tint name now appears on the rail row and tab tooltips.
    • Settings screens follow the Light and Solarized Dark themes (D6). ScreenTheme is passed through every view; there is no global state. Dark is byte-identical.
    • 13 settings views were added to FrameContrastTests.

Testing

  • dotnet build -c Release SharpMUTerm.slnx: 0 errors.
    • It warns NU1903 for SixLabors.ImageSharp 3.1.12 (GHSA-v76p-62qx-wwq2). This is a new advisory, not caused by this PR.
  • Suites:
    • Core 1420/1421. AReadOnlyCacheDirectory_DegradesToMemoryOnly fails only because this sandbox runs as root.
    • Graphics 99/99, Scripting 68/68, Web 37/37, Client 18/18, Tui 2175/2175, Avalonia 222/222.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx

The status row wrapped to a second row at 80x24, the meters vanished below
120 columns, and F2/F4 drew their editors off the right-hand edge at 80.
The status row now drops its encoding and palette hints before wrapping,
meters fall back to a compact percentage form, two-column screens too
narrow for both columns show the one the keyboard is in, and header/footer
bars elide text rather than clipping their keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
--plain had no way to add a world on a first run: the settings screens are
not usable there. /connect <host> [port] [tls] saves a new world and dials
it; /connect <name> opens a configured one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
A machine with no Nerd Font installed drew every chrome icon in the window
as a box, and text fell back to whatever monospace face the system had.
The window now carries JetBrains Mono (OFL 1.1) and Symbols Nerd Font Mono
(MIT) as embedded resources, used after any preferred installed face, and
ships their licences beside the executable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
A header too narrow for its hints now shortens them from the middle, so the
key that closes the screen stays on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
@coderabbitai

coderabbitai Bot commented Nov 4, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

… availability

Core only, no UI wiring. A layout (Layouts/LayoutDefinition) places panels by id
in regions with an order, a relative size, a collapsed flag and an optional
tab host. Roleplay and Combat are built in and read-only; LayoutLibrary saves,
updates, renames and deletes the player's layouts in AppConfiguration.Layouts,
turning an update of a built-in into a saved copy. LayoutResolver picks the
character's layout, then the world's, then the world's accepted suggestion,
then Roleplay, passing over ids that name nothing. LayoutSuggester suggests
Combat on vitals and Roleplay otherwise, saying whether the MSSP codebase was a
MUSH-family one. PanelAvailability says which panels have data for a session,
and LayoutFold folds a layout into the workspace's own split tree.

New config fields are optional with old-behaviour defaults, so no migration
and no schema version change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…mands from it

A new UI-agnostic project, SharpMUTerm.Client (Core only), holds the shared client shell's first
piece: ContributionRegistry, with six kinds of contribution (commands, panels, settings sections,
status cells, overlays, output decorators), each with a stable id unique within its kind and an owner.

The built-in ⌃P catalog moves out of Core's CommandCatalog.Build into BuiltInCommands, registered as
contributions; the Tui supplies the work (RunBuiltInCommand). The command surface, the pointer menu
and DispatchCommand all read the registry, so a package or host can add commands. The catalog is
unchanged entry for entry. New suite: SharpMUTerm.Client.Tests (in CI).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…match kinds, and a live test on F2

Ctrl+P > "Make a trigger from the selection" (and the output right-click menu) makes a rule from the
selected line, with numbers, quoted speech and known GMCP player names turned into * wildcards, and
opens F2 on it with the pattern being typed and the regex shown behind it. Triggers gain a match type
(contains the text, starts with, * wildcards, regular expression; regex stays the default), and F2's
pattern editor shows how many of the last 2,000 held lines match, with the newest three highlighted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…ind readers

Settings screens (D6): ScreenPalette's constants become roles spelt as the
Dark theme's values. A new immutable ScreenTheme, built once from the app's
theme, resolves renderer markup and control colours at the view boundary
(ScreenChrome.Panel/Band/VerticalRule, ScreenTheme.Colour), so renderers and
their tests are untouched and there is no mutable global. Dark resolves to
the constants byte for byte; Light mirrors L*, Solarized takes its
background's hue, and every ink is held to Contrast.Floor on its planes.
Threaded through every *ScreenView.Build (required), SettingsOverlay,
ComposeOverlay, ListOverlay and EditReviewOverlay. FrameContrastTests gains
13 settings views on all three themes; ScreenThemeTests checks that no Light
settings frame paints a dark-palette plane.

Tints: ColourVision (Core) simulates protanopia/deuteranopia/tritanopia
(Machado 2009) and measures CIEDE2000. The ten anchors were re-picked within
40 degrees of each name: the worst dichromat pair goes from 0.28 to 1.99, and
the worst pair for typical vision from 3.71 to 3.96. TintColourVisionTests
holds 1.9 / 3.5 on all themes. Names kept. A tinted character's rail and tab
tooltips name the colour, at no cell cost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
@HarryCordewener HarryCordewener changed the title UX fixes: 80-column layout, /connect, bundled window fonts UX: fix-now batch, layout model, Client registry, trigger builder, Light settings Nov 4, 2026
claude added 17 commits November 4, 2026 03:38
…rnings

The palette's ranking and grouping move into SharpMUTerm.Client
(CommandPaletteModel, FuzzyMatch, RecentCommands, KeyGlyphs) so the window's
palette can list the same rows; Core's CommandMatcher is removed. Matching is
word-by-word subsequence with word-start/consecutive/prefix scoring and
highlighted matches. An empty query lists RECENT (in memory, eight) and
SUGGESTED (from CommandContext) before the groups. Keys sit in a right-aligned
column in one glyph vocabulary and are never elided.

F4 marks a binding that wins over a client key (MacroKeys.YieldedBy) with an
amber triangle and a shadowed duplicate as dead; ⌃P lists "Key clashes (N)"
while N > 0. New snapshot views menu-fuzzy and menu-recent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…uggestion

A character's resolved layout is realised as one pane per panel that has data
(LayoutFold -> LayoutRealiser -> WorkspaceLayout.ReplaceTree); output, chat and
map are the existing windows, every other panel a panel:<id>:<key> window whose
control comes from a factory registered in TerminalPanels. Re-folds are rate
limited and rebuild only when the drawn panels change, and stop once the player
rearranges the panes. A restored workspace wins at startup; only the live entry
point applies layouts on its own. Alt+M and Ctrl+P > Layouts open the list
(apply, save changes, save as new, use for world/character); F5 gains a layout
row on worlds and characters; a world's first connect accepts a suggestion and
says so once. LayoutCapture reads an arrangement back into placements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…, meter table

F7 and F8 are drawn from settings sections declared in SharpMUTerm.Client
(BuiltInSettings): each field has a group heading and a SettingBinding over
the configuration, whose default is read off a fresh AppConfiguration. F7 is
now "Text & display", grouped under Colour & legibility, Text, Links &
pictures, Status row & meters, Activity and Sound, and windowed to the
screen height so nothing runs off a 24-row terminal.

- `/` on any settings screen opens a search over every declared field and
  every screen title, with a per-section match count; Enter goes to the
  field on its own screen, Esc puts the screen back.
- A field away from its default is marked "● changed"; `r` resets it
  through the same save funnel. The header names `r reset` only there.
- F7's meters are a table: label, value path, maximum path and a live
  `now` column from the focused session's game data, [+ meter], Delete to
  remove, a box of received paths while the cursor is in the table, and
  the path fields offer those paths with their values.

Snapshot views settings-search, settings-meters, settings-meters-pick and
settings-changed, in the parity list and FrameContrastTests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…hread-izbu0c

# Conflicts:
#	CHANGELOG.md
#	src/SharpMUTerm.Tui/SharpMUTermApp.cs
Four panels plug into the layouts seam (TerminalPanels.Register from
RegisterTerminalPanels): who is here from GMCP Room.Players and the room's
item list (a click puts look <name> on the command line), a one-row vitals
strip of reserved fields with bars, values, a low mark and the combat
target, a quick bar of function-key macros run through the key's own path,
and the F1 editor docked as a pane sharing F1's draft. While the composer's
pane is in front it is armed: the focus pin targets its editor, Esc hands the
keyboard back to the command line, and arriving at the pane arms it again.
Each is on Ctrl+P and has a snapshot view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
A headless app runs OnUiThread inline, so the picture host's deliveries ran
on the thread pool while the test or snapshot appended the next line. The
reserved-picture draw and the append that reads ReservedPicture.Drawn raced
over the pane buffer, and a row could be left blank: the intermittent
APictureStopsAtTheFirstLineWithoutItsCells. Headless picture work is now
queued and run at the next frame or while InlineImagesSettled is awaited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
… cells

The window host draws native views in a layer above the cell view, fed through
IPointerPresenter (the scrollbars' channel): a Layouts button over the header's
three trailing blank cells, whose menu is built by the client from the layout
library's registry commands; native vitals gauges drawn from the VitalsModel the
cell strip now renders from; and a native map canvas (zoom, pan, click to walk
through the client's speedwalk). The cells underneath are unchanged, so no pane
rectangle moves and host parity is untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
Scrolling a pane back now splits it inside its own rectangle: the history
being read on top, a band ("scrolled back N rows · End returns · ⌥F
freezes"), and the newest lines still arriving below. It reuses freeze's
two-viewport layout with the window's own control and viewport kept on top,
so the scroll keys, wheel, search landing and status row are unchanged; the
tail is a re-fed copy of the buffer's last lines and indexes nothing. A
split pane is not "scrolled back" for unread badging. F7 ▸ SCROLLBACK turns
it off.

F5 ▸ join wraps (per world, off by default) joins rows a server broke at
its own column, at ingest and before the triggers, through a conservative
ServerWrapJoiner: full row, unpunctuated end, lower-case continuation, same
style, no prompts, structure or column layouts. A full row is held until
the next line or 250 ms of quiet.

New snapshot views: scrollback-split, reflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
The vitals strip and the quick bar are exactly one row, with no tab strip over a
one-tab strip, and the docked composer has an editor's floor. A pane's height
claim (PaneNode.FixedRows/MinRows) is derived from what it holds on every
rebuild (PanelSizing) and honoured by the grid and by LayoutSolver
(PaneExtent.DistributeRows); a divider beside a fixed strip neither hovers nor
drags, and the resize keys take rows from a pane beyond it or refuse out loud.

The status row leaves its meters out while the vitals strip is showing them.
Layout panels are WindowKind.Panel: no rail row, no Alt+number, no Go to entry,
so map and capture windows keep their digits. Alt+M renames (r) and deletes
(d, then y) saved layouts. New snapshot view: layout-roleplay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…onnect

A client with no worlds opens on a welcome: connect to a host (a form that
goes through /connect's own path, adding the character it names), browse
games from the package index (still fetched only when asked; says so in
place when no index is set), or open F5, with the /connect line spelt out
and a starting layout to pick (suggested by default, so the client's
suggestion still applies). Esc closes it for the run; /welcome and
Ctrl+P > Welcome reopen it. Under --plain (ClientHost.ScreenIsSeen false)
the choices are announced as commands and /welcome layout picks the layout.

The first connect of a configuration that started empty puts one chrome
row naming Ctrl+P, Alt+M, F1 and F5, owed through
AppConfiguration.KeyTourPending so it is shown once and survives a
restart. Ctrl+P > Key tour hides and shows it.

Snapshot views welcome, welcome-connect and tour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…hread-izbu0c

# Conflicts:
#	CHANGELOG.md
#	src/SharpMUTerm.Tui/ListOverlay.cs
#	tests/SharpMUTerm.Tui.Tests/FrameContrastTests.cs
In a stacked split no row is spent on a divider above a pane that draws its
own tab strip: the strip is the boundary, and its empty run past the last
tab is the divider's handle (hover cue, drag, double-click to even, tooltip).
Fixed strip panels sit flush with no divider on either side and, unfocused,
are recessed a step so they never share an unfocused pane's plane. Side by
side is unchanged. The freed rows go to the panes (a NAWS change).

PaneExtent.DividerRows is the one rule, read by LayoutSolver, PaneExtent,
PaneResize and the terminal's grid. Adds the split-stacked snapshot view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
claude added 22 commits November 4, 2026 05:36
Built from the Client setting declarations, with the shared search,
reset and meter table. Other sections open their cell screens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…he window

HeaderModel and SidebarModel (Client) are what the cell header and rail are
rendered from, and what the window's NativeHeader and NativeSidebar draw over
those cells. Clicks hand back the model's own targets to the rail's handler and
the palette; cells, frames and pane rectangles are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
The window draws a native who-here list (presence dots, idle text, NPCs
dimmed, hover plate, full-name tooltip) and native quick-bar buttons
(+N overflow menu, F4 edits keys link) over their panes, from new
host-neutral WhoHereModel/QuickBarModel in SharpMUTerm.Client that the
cell renderers now draw from too. A click is PanelOverlay.Pick(index),
read back by the client from what it published and acted on through
the cell links' own methods on the panel's character; the window builds
no command and never takes keyboard focus. Native panels are withdrawn
while a modal is open, so they no longer sit on top of F4 or Ctrl+P.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…hread-izbu0c

# Conflicts:
#	src/SharpMUTerm.Avalonia/DesktopLayer.cs
The window host gets ClientHost.NativePalette (INativePalette, null in the
terminal, snapshot, tests and parity harness). ToggleMenu/OpenMenu are the one
funnel: Ctrl+P, the native header's search box and brand, the pointer menu's new
"More commands..." entry and the menu* views all ask the host first. The client
captures a PaletteSession (Client) on its own thread; the window's PaletteOverlay
filters it with CommandPaletteModel, so both hosts list the same rows, and runs
the chosen id back through RunFromPalette so recents are recorded identically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…09104d18e798

# Conflicts:
#	src/SharpMUTerm.Avalonia/MainWindow.cs
#	src/SharpMUTerm.Avalonia/WindowHost.cs
#	src/SharpMUTerm.Tui/Hosting/ClientHost.cs
#	src/SharpMUTerm.Tui/SharpMUTermApp.cs
#	tests/SharpMUTerm.Avalonia.Tests/HostParityTests.cs
ClientHost.Chrome (HostChrome(Header, Rail), required) says which chrome the
host draws; the client then leaves no cell for it (hidden header band, no rail
column), so the veto, the layout, PaneOutputRects and per-pane NAWS follow from
arranged bounds. The window draws a 42 px header and a 232 px sidebar (48 px
folded) and moves TerminalView into what is left; modals draw in the cell area
with the chrome inert beside them, and an armed Ctrl+B shows its keys in the
native header. The header's search box opens the palette and a second click
while it is up is not a refusal. HostParityTests compares both hosts under
both arrangements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
SurfaceCatalog (Client/Surfaces) declares every surface a host may draw
with its own controls - header, sidebar, folded rail strip, armed Ctrl+B
keymap, vitals, map, who-here, quick bar, settings, the Layouts menu and
Ctrl+P - with its Client model, its actions and the client method each
ends in, how each host draws it, its cell renderer and its native views.

Three suites hold the hosts to it:
- SurfaceCatalogTests (Client): ids, hosts, models in Client/Core, a
  native-only door names where the cells offer it.
- SurfaceCellTests (Tui): every cell renderer takes its model, every
  action's method exists, every command resolves; F4's terminal verdicts.
- SurfaceParityTests (Avalonia): every Control in the window is declared
  and handed its model; each native model equals the one the cells drew
  in the same frame; each native action and its cell link leave two
  clients in the same state; F4 and the quick bar's window verdicts.

F4 and the quick bar are per host: MacroKeys.Verdict and the keypad,
clash and quick-bar code take HostKeys, and HostKeys.ReportsChordsWhole
says the window delivers Ctrl+digit and friends as themselves.

Divergences fixed on the way: the quick bar's "F4 edits keys" is a cell
link too; the map's anchor with no current room is one rule (and the
native map gets the model instance the cells drew); the Layouts menu is
a MenuModel whose choices come back through HeaderAction.Dispatch and are
refused unless offered; the Ctrl+B keymap is one model (exit hint
included) for the cell panel and the native header; the sidebar's fold
button has a Ctrl+P command, layout:toggle-rail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…us row

Below 100 columns the connection rail folds itself to its strip (whichever rail is drawn,
through SidebarModel.Collapsed; the window measures its whole width via
IPointerPresenter.WindowColumns so the fold cannot undo itself), acting only when the width
crosses the line so a player's own fold wins. Below 100 columns or 30 rows an empty command
line is one row and grows as before; a growing bar now takes back the editor's scroll.

While the focused pane is scrolled back, split, frozen, carrying the search bar, or a docked
composer holds the keyboard, the status row names that mode's keys (ModeHints, Client),
resolved through the host's spellings, in place of the pane hints and only where they fit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
… both maps

A notice is now a Client model (NoticeModel: text, severity, optional action
command, time). The terminal draws the status row from it exactly as before;
the window also shows it as a toast bottom-right above the pane area, held
while hovered, faded after six seconds, never focused, and a click runs its
action through the registry (refused unless a notice offered it). Declared as
the `notice` surface with model and action probes in SurfaceParityTests.

MapViewModel carries area colours (MapAreaPalette: stable per area name, dark
and light sets that clear the floor on every theme), stairs flags and the area
an exit leads to. The cell map colours rooms through Contrast.Legible and draws
a one-line legend; the native map adds names at zoom, a hover plate, stairs
marks, a current-room halo and a legend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…hread-izbu0c

# Conflicts:
#	CHANGELOG.md
#	src/SharpMUTerm.Tui/Pointer/PointerTarget.cs
F7 opens with a THEME group: one row per theme (built-ins, the customised
theme in force, CustomThemes) with a seven-row preview of the theme under
the cursor - an ANSI pane, the focused pane with a tab strip, a tinted pane
and the armed command line - drawn from one Client model (ThemePreview /
ThemeCard, built by Tui ThemePreviews). Moving the cursor paints the screen
in that theme; Esc closes with nothing committed; Enter commits through
ScreenEdits.Apply, and SaveConfiguration puts the new theme into force
(ApplyThemeIfChanged / ApplyTheme), with a notice that output already on
screen keeps its colours. The desktop settings window draws the same model
as native cards (ThemeCardView); a click uses the theme and the window
follows the variant.

SettingBinding now reads a SettingTarget (configuration, world,
character) with ForWorld/ForCharacter factories and a section Scope. F5's
world fields and F9's character fields are declared (BuiltInSettings.World
and .Character) with TextRules shared by both surfaces. The cell F5 builds
its world, security and character rows from those declarations in its own
ordinal order; the settings window edits them with a subject picker and a
door to the cell screen for the lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
A theme change now formats every pane line again against the new reading
plane, in place: a PaneLine keeps the StyledLine it was formatted from
(Source) and a chrome bar the function that draws it (Bar), so freeze,
away/missed/search bars, picture anchors and the selection all survive.
About 106 ms and 743 KiB at the 2,000-line pane cap. Space on a theme row
does what Enter does.

Alt+C moves the keyboard into the connection sidebar in both hosts
(SidebarModel.Cursor, layout:rail-keys, the rail-keys view): arrows, Home
and End walk, Enter goes, Esc or any other key returns. The cell rail
bands the row; the native sidebar rings it. The native sidebar scrolls
with the wheel and an overlay bar, and a right click on a row opens the
client's menu of registry commands for it (SidebarRowMenu), which the
cell rail's pointer menu also uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
F1 opens a native TextBox editor over the cell area and the docked composer
is a native editor over its pane, both drawn from one Client ComposerModel
(target, draft, escaping, MOO edit, dictionary, marks as ranges, key row) that
the cell composers now render from too. Gestures go back through a
ComposerSession to the methods the cell keys end in, so ⌃S sends the same
command (ComposeMessage.Build) and a MOO edit saves its lines. Misspellings
get a real wavy underline from the same SpellChecker, with suggestions and
Add to dictionary in the right-click menu.

New host member ClientHost.NativeComposer (INativeComposer); the terminal,
headless and snapshot hosts pass null. Declared in the surface catalog
(compose, composer) with model comparison and wire-level outcome probes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
…il editor

A list declaration kind in Client (SettingsCollection, item-scoped bindings,
SettingsContext): add, duplicate, delete, reorder, enable and move to another
set, every change through the form's one commit and replacing lists rather
than editing them. Package sets are listed read-only with copy-to-edit.

The cell screens build their rows from the declarations with every ordinal
and frame unchanged, plus Alt+Up/Down to move an item within its set. The
desktop window draws each list section as a master-detail editor with the F2
live test under the pattern, and the trigger builder opens it on the new rule.
F4's key field asks the host's own MacroKeys verdict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqqt7SPYixp6Cg4JDKRTwx
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.

2 participants