Build the settings vocabulary and generate the keymap
Stage 3 and 4 of docs/superpowers/plans/2026-08-17-panama-cohesion.md. Add SettingsPage plus ToggleRow, SliderRow, ChoiceRow, ActionRow, and TextRow. A row names a schema key and needs nothing else: label, detail, range, and unit come from PreferenceSchema, and writes go through SystemSettings.commitPreference, which routes compositor-backed keys through apply-and-verify and local keys straight to the store. The page scaffold that was copy-pasted eleven times is now one component. Rebuild Appearance around a live preview of the real desktop, scaled by the ratio between the preview and the actual monitor so a 10px gap on a 4500px display looks as small as it is. Rebuild Desktop & Dock and Input & Shortcuts on the shared rows, replacing the read-only text that stood in for controls that were merely expensive to add. Generate the shortcut list from hyprctl binds. The page held a hand-typed nineteen entries against a real keymap of a hundred and thirteen; it could not show the rest and went stale whenever a bind changed. Every bind now carries its own description -- backfilled for the twenty-nine that lacked one -- and keybinds-contract.sh fails if any bind lacks one, since undescribed binds are dropped from the page. Make Restore defaults span every store Panama owns. Resetting only the schema store left the Home accessory arrangement customised while claiming to restore defaults, which is worse than no reset because it is silent. Done through HomePreferences' existing public aliases rather than a new API. Four defects found while building: cursor:inactive_timeout is answered by getoption as float, not int. A wrong readAs does not fail loudly; it makes every write to that key look rejected, and the user saw an error for a change that worked. schema-hypr-shape-contract.sh now checks all 23 mapped options against the running compositor. The Settings window is tiled, so implicitWidth is only a hint and rows must survive roughly 400px. SliderRow stacks its control under the label below 520px. Binding an anchor to undefined to switch layouts does not reliably release it. Both row layouts are positioned explicitly. Concurrent compositor writes are queued and merged rather than refused. The startup replay of every compositor-backed preference routinely overlaps a UI change, and refusing left the store and the compositor disagreeing. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
This commit is contained in:
@@ -216,14 +216,38 @@ Singleton {
|
||||
}
|
||||
if (Object.keys(requested).length === 0)
|
||||
return false;
|
||||
|
||||
// A write in flight is queued rather than refused. Options are applied
|
||||
// and verified one batch at a time, but the callers are a settings UI
|
||||
// and a startup replay of every compositor-backed preference -- they
|
||||
// overlap routinely, and dropping a change on the floor would leave the
|
||||
// stored value and the compositor disagreeing. Later values for the
|
||||
// same key win.
|
||||
if (configWrite.running || configVerify.running) {
|
||||
root.lastError = "Another change is still being applied.";
|
||||
return false;
|
||||
root.queued = Object.assign({}, root.queued, requested);
|
||||
return true;
|
||||
}
|
||||
|
||||
root.startWrite(requested);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Merged batches waiting for the current write to finish.
|
||||
property var queued: ({})
|
||||
|
||||
function startWrite(requested: var): void {
|
||||
configWrite.pending = requested;
|
||||
configWrite.exec(["hyprctl", "eval", root.buildConfigPayload(requested)]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called when a write settles, however it settled. A failed batch must not
|
||||
// strand whatever queued up behind it.
|
||||
function drainQueue(): void {
|
||||
const next = root.queued;
|
||||
if (Object.keys(next).length === 0)
|
||||
return;
|
||||
root.queued = ({});
|
||||
root.startWrite(next);
|
||||
}
|
||||
|
||||
// The value as Hyprland stores it. Several options are a toggle in the UI
|
||||
@@ -308,6 +332,7 @@ Singleton {
|
||||
}
|
||||
|
||||
root.lastError = rejected.length === 0 ? "" : `Hyprland did not apply ${rejected.join(" or ")}.`;
|
||||
root.drainQueue();
|
||||
}
|
||||
|
||||
function matchesObserved(entry: var, value: var, answer: var): bool {
|
||||
@@ -334,6 +359,57 @@ Singleton {
|
||||
function reportWriteFailure(requested: var, text: string): void {
|
||||
const labels = Object.keys(requested).map(key => PreferenceSchema.spec(key).label);
|
||||
root.lastError = `Hyprland rejected ${labels.join(" and ")}.`;
|
||||
root.drainQueue();
|
||||
}
|
||||
|
||||
// The one entry point the settings UI uses to change any preference.
|
||||
//
|
||||
// A compositor-backed setting must be applied and verified before it is
|
||||
// stored, so that preferences never claim a value Hyprland refused.
|
||||
// Everything else is a direct write. Rows bind a schema key and call this;
|
||||
// they never need to know which kind they are holding.
|
||||
function commitPreference(key: string, value: var): bool {
|
||||
const entry = PreferenceSchema.spec(key);
|
||||
if (!entry) {
|
||||
root.lastError = "That setting is not part of Panama.";
|
||||
return false;
|
||||
}
|
||||
if (entry.hypr) {
|
||||
const batch = {};
|
||||
batch[key] = value;
|
||||
return root.applyOptions(batch);
|
||||
}
|
||||
return DesktopPreferences.set(key, value);
|
||||
}
|
||||
|
||||
// Restores shipped defaults across every store Panama owns, not just the
|
||||
// schema. Panama keeps user state in more than one file -- the schema store,
|
||||
// the focus session, and the Home accessory arrangement -- and a reset that
|
||||
// silently skipped one would be worse than no reset at all.
|
||||
//
|
||||
// Compositor-backed values are re-applied afterwards, since resetting the
|
||||
// stored value does not by itself tell Hyprland anything.
|
||||
function restoreDefaults(): void {
|
||||
DesktopPreferences.resetDesktopDefaults();
|
||||
|
||||
// Home accessories keep their own store (panama-home.json), so a reset
|
||||
// that only cleared the schema store would silently leave a customised
|
||||
// favourites list behind while claiming to restore Panama's defaults.
|
||||
//
|
||||
// Done through HomePreferences' public writable aliases rather than a
|
||||
// reset function of its own: clearing `favorites` and returning
|
||||
// `initialized` to false is exactly the state a fresh install has, and
|
||||
// it lets initialize() seed the list again on next use.
|
||||
HomePreferences.favorites = [];
|
||||
HomePreferences.initialized = false;
|
||||
|
||||
resettleTimer.restart();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: resettleTimer
|
||||
interval: 60
|
||||
onTriggered: root.applyPersistedDisplayPolicy()
|
||||
}
|
||||
|
||||
function setAutoHdr(enabled: bool): void {
|
||||
|
||||
Reference in New Issue
Block a user