Skip to content

feat(profiles): add a settings strip and live profile switching - #44

Open
inayayousfi wants to merge 14 commits into
refactor/generic-runtime-actionsfrom
feat/settings-strip
Open

inayayousfi wants to merge 14 commits into
refactor/generic-runtime-actionsfrom
feat/settings-strip

Conversation

@inayayousfi

@inayayousfi inayayousfi commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

This sits on top of #36 and depends on its state registry, where every setting is a declared variable that any value option can bind to.

The idea is to let a profile put a strip of controls at the bottom of any window, with checkboxes, steppers, dropdowns and menus that read and write those variables. The profile decides what the strip shows and what each control does. These controls are axidev_osk.std helpers built from the node kinds that already exist, so the engine gets no menu concept of its own.

Dropdowns and menus open as panels inside the window instead of Qt popups. I tried real popups on a layer-shell window under KDE Plasma Wayland, and LayerShellQt 6.7.5 refused both a dropdown list and a menu with "Cannot attach popup of unknown type". An inline panel does work there, but the window never shrank back after the panel closed, so the engine has to resize it to its minimum when content shrinks.

Profiles also switch while the app runs, through a profile.switch action. A window whose id exists in both profiles keeps its position, and a profile that fails to build rolls back to the previous one.

To show all of it, the default keyboard gets a strip with a profile dropdown, an opacity stepper and a panel holding every dwell option. A second profile named social has four keys (Up, Down, L and K) for scrolling a feed, and its strip only offers the way back. Opacity and the dwell settings live in the root config, so they survive a switch.

Open risks: opening a panel re-centers the keyboard on layer shell, so keys move under a dwell pointer while it is open. Live theme bindings have not been measured, and keeping a window's position across a switch is untested on every backend.

inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

A profile can put a strip of checkboxes, steppers, dropdowns, and menus
at the bottom of any window, built in osk.std from existing node kinds
and bound to registry variables. Dropdowns and menus open as inline
panels because Qt popups fail on layer-shell windows.

Profiles switch at runtime, keep a shared window's position, and roll
back when the new one fails to build. The default keyboard gets a
settings strip, and a small social profile gives switching a target.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

A window now refits to its content: when a panel shows or hides, it
resizes to its new minimum size, so it grows for the panel and shrinks
back afterwards.

osk.std.controls adds checkbox, stepper, menu, dropdown, and strip,
built from existing node kinds and bound to state. Menus open as inline
panels; menus in one group close each other. A strip shows only a small
⋯ button until it is opened, then its controls beside it and their
panels above it.

profile.switch runs another profile of the root config once the queue
is empty. It closes the old windows and drops their bindings at once,
reopens a window with the same ID where it was, and builds the previous
profile again when the new one fails. app.profile and app.profiles are
read-only, a profile without hot corners turns them off, and a prepared
lock-screen panel refuses a profile without one.

Every window of the bundled config gets the same strip, in place of its
content's bottom margin: profile, keyboard opacity, and dwell settings.
A social profile with Up, Down, L, and K gives switching a target.
AGENTS.md documents the strip and switching, and the plan's steps are
done.
…tching

inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

A profile switch now runs in two steps. profile.switch tears the running
profile down, releasing held keys, then sends profile.start, which also
waits for an empty queue, so the closing windows' reports reach no
profile. A switch requested in between only replaces the target, so the
last one requested starts and two queued switches no longer loop. When
the previous profile cannot start again after a failed switch, the
report's exception type is ProfileRollbackFailed.

Startup and a switch show start windows through one helper, and a
floating window restored or moved off screen is moved back inside it.
NodeBuilder.build_tree returns a tree's widget with its unbinds, and a
window drops them all when Qt deletes it, so a failed build leaves no
bindings behind. Setting window factories no longer closes windows, and
starting attachments no longer stops them.

SETTINGS_STRIP_PLAN.md now matches the code: menus keep the open menu's
ID in std.menus.<group>, and every window of both profiles has the full
strip. AGENTS.md describes the two-step switch. The default profile's
parity data is unchanged.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Between two profiles, a window action now reports action.failed
instead of building one of the old profile's windows, which the new
profile then reused. Tearing a profile down empties the window
factories, and stopping attachments forgets the lock-screen panel's
window. Starting attachments while others still run, or replacing
window factories while windows are live, raises instead of relying on
the caller's order.

Startup still shows its windows outside a queue drain. That is safe
now that a binding failing while it is bound posts its report, and the
docstring says so. A nested build_tree keeps the outer build's
bindings.

The bundled profiles and the profile menu read one table of names,
captions, and builders, so the two cannot disagree. Smaller cleanups:
the position restore trusts the state registry's types, a float is no
longer converted to float, and the strip docstring wraps evenly.
AGENTS.md describes the gap between two profiles.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

SETTINGS_STRIP_PLAN.md is deleted because its steps are done. AGENTS.md
adds profile switching to the lifecycle the engine keeps; the switch
itself is described in the runtime's docstrings.

ConfigDecoder.decode_root only wrapped decode_config for tests, so the
tests call decode_config(...).active instead.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Brings in timers, hover, pointer reports, and the move of key looks to
std. The settings controls now light their buttons through std.lit
instead of the removed latched option, and std exports controls beside
lit and styles. A profile switch drops the old profile's timers through
the profile's stop hook.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Brings in the dwell tests' wait for clicks.
@inayayousfi
inayayousfi marked this pull request as ready for review October 13, 2026 15:16
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Brings in the dwell indicator options and the close_label window value.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Brings in the interaction reports: mouse buttons, title-bar and grip
drags, and finished dwells, with std giving them their usual meaning.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Brings in optional built-ins and their raw reports: any-button drags,
hover, wheel, and geometry reports, hot-corner and dwell options,
std.locator, and window anchors.

Both bundled profiles color their keys through std.locator, so the
settings-strip buttons glow the gap color. keep_on_screen now also
governs the window's keep_on_screen method, so a restored position or
a grown window stays where it is when the option is off.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

A content change now refits the window through the shared _refit, so
fit_content governs it too, and the window stays on screen after any
refit.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

A window's fixed keep_position field (default on) decides whether a
profile switch builds it where a window with the same ID last was.
With it off, the window starts where its overlay places it, and the
profile can place it itself with window.move_to and the observed
windows.<id>.position.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Brings the Windows mouse-button fix.
inayayousfi directed the work and made every decision.
Claude Opus 5.5, running in Claude Code, carried inayayousfi's decisions out.

Brings plain-window moves and resizes.
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