Hyprland 0.57 refuses a bind on a bare modifier and an empty input:scroll_method. The switcher's Super-release commit now binds Super_L and Super_R, which also brings back every bind below it in keybinds.lua. An empty scroll method is left unwritten, so libinput still picks per device.
config/bash/shell exported SSH_ASKPASS=ksshaskpass with
SSH_ASKPASS_REQUIRE=prefer on every machine. `prefer` means ssh and git reach
for that dialog BEFORE the terminal prompt, so on a headless server the
prompt that would have worked was skipped in favour of a KDE window that
cannot open:
error: /usr/bin/ksshaskpass died of signal 6
fatal: could not read Username for 'https://git.gbrown.org'
A push from a server failed to ASK for a username rather than failing to
authenticate. The shell environment is the one half of Panama a server does
install, so nothing downstream was going to catch this.
The export is now conditional on a session existing to draw in, and on the
binary being there to run. The headless branch also CLEARS both variables
rather than merely declining to set them: an askpass inherited from a parent
that had a display, or from a login predating this guard, is exactly as
unusable and a guard that only withholds leaves it in place. A fresh login
shell on the repaired server still reported prefer until it cleared them.
A desktop is unchanged; a server, a serial console and a plain SSH session
fall back to the terminal prompt, which works.
tests/setup/headless-askpass-contract pins every branch, the inherited case
included, and fails against the previous code and against a guard that only
withholds.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The scan was keyed off this item becoming visible, but on a machine with no
backlight `visible` stays false until a scan has already found a DDC
display, so it never scanned at all. It now keys off the quick settings
panel opening, which still keeps the second-long I2C probe off shell
startup.
This panel's DP link is marginal. 4500x3000@60 at 10bpc is around 24 Gbps,
right at the edge of DP 1.4 HBR3 and reliant on DSC, so every modeset
retrains the link and blanks the screen. 8bpc keeps headroom.
Steam games are the other trigger. Everything Hyprland does only for a real
fullscreen window (direct scanout, VRR, tearing, auto HDR) forces that
retrain, so steam_app windows get fullscreen_state "1 2": maximized
internally while the game believes it is fullscreen, which is what
borderless windowed looks like from the game's side.
Recorded alongside the related Panama settings already at 0,
directScanoutPolicy and vrrPolicy.
The picker paints an opaque frozen frame edge to edge, so the blur and dim
it inherited from the shared qs-overlay rule were compositor work on pixels
nobody sees. It now has its own layer rule.
no_anim is the load-bearing part. Capture.qml unmaps the picker and runs
grim 90ms later, and the default 200ms layersOut fade put the half-faded
picker, dimming and all, into every screenshot.
Quickshell's exclusiveZone setter flips exclusionMode back to Normal as a
side effect, so a window declaring both is at the mercy of which property
the QML engine applies last. The 2026-09-14 Qt update changed that order
and every full-screen overlay slid under the bar.
Each overlay now declares ExclusionMode.Ignore alone. The capture picker
was the visible failure: it started under the bar, the full-output freeze
frame was squeezed into a shorter box, and every selection landed one
bar-height off in the real capture.
tests/quickshell/exclusion-idiom-contract fails any file that pairs them.
eza 0.23.5 gives --icons an optional WHEN value, so a trailing --icons
swallowed the path argument and `ls docs` failed with "invalid value
'docs' for '--icons'". Using --icons=auto makes both aliases independent
of flag order.
Claude-Session: https://claude.ai/code/session_017trjCxkXTdv7Z5ePfGWYtW
OpenAI ships an official Linux RPM now, so the community wrapper goes away:
`panama app chatgpt-desktop` built codex-desktop from the upstream macOS disk
image and ran a local rebuild daemon to keep it current, and the official
package comes from a repository that upgrades with everything else. The app
file, the help example and the dock's pinned id all move over, and a migration
replaces the build on machines that already have it -- official package on
before the community one comes off, so a failure part-way still leaves an app.
The install itself does not follow upstream's instructions. Those are "download
this RPM and install it", and the RPM's own root scriptlet is what writes the
repository file and drops the signing key into /etc/pki/rpm-gpg -- so root runs
an unverified download and then learns from it what to trust. That is the shape
the repository audit forbids: no network response is executed as root without a
verified digest or signature first.
OpenAI publishes no key and no fingerprint anywhere an install could fetch and
check them, so the key is pinned here instead. setup/keys/ carries it and says
where it came from, including the honest part -- this is trust established on
first use and then held, not trust verified against the publisher. setup/lib/
chatgpt-package verifies that copy's fingerprint, installs it, and writes the
repository with gpgcheck and repo_gpgcheck on before anything is installed, so
dnf checks the metadata signature and the package signature itself. It is byte
for byte the repository the scriptlet would have written, so nothing churns
afterwards, and every later upgrade goes through the same key. Both callers use
it; a verification failure skips ChatGPT rather than installing it anyway.
The contract proves the pinned key is the key the library names, that a
missing, unreadable or mismatched key writes nothing at all, that what is
written actually turns the checks on, and that neither caller hands root a
downloaded RPM.
Claude-Session: https://claude.ai/code/session_017zzbtfnMLoYrB8WesqANFY
CLAUDE_CONFIG_DIR points the CLI at a different config home, so `klaude`
runs Claude Code against ~/.klaude -- its own settings, auth and history --
without disturbing the default one this machine already uses.
Claude-Session: https://claude.ai/code/session_017zzbtfnMLoYrB8WesqANFY
Popover's container is a plain Item and never measures its children, so
the three-dots menu on a notification card opened as a 240x28 sliver
with its one row clipped out of existence. TrayMenu and AgentUsagePanel
both carry the antidote — implicit sizes on the Popover itself, taken
from the content column — and now the notification settings menu does
too, with rows filling the window width so the hover reaches the edges.
Claude-Session: https://claude.ai/code/session_01W8icivxZcmFTR2g6DkYhjT
Third recurrence of the stale-mount bug: the ExecStartPre cleanup ran on
clean ground and the portal still came up mountless, sitting active while
every fresh flatpak sandbox failed to build. Add an ExecStartPost that
polls for the fuse mount and fails the start after five seconds without
one, so a mountless portal shows up failed in the journal and the next
bus activation retries from the cleanup instead of lingering broken.
Claude-Session: https://claude.ai/code/session_01CTL7G5u3iDf1NCM6rTyCES
.bashrc is back to its one job: export the Panama paths and source
what it finds. The personal-env block moves into config/bash/shell —
first, because the tmux guard below it reads that file — and the
cargo source that was duplicated between the two files lives only in
shell now.
The real fix is behind that tidying: both Home Assistant helpers
defaulted to the IN-REPO config/bash/env, and the writer rebuilt it
with only its own three lines — which read as "my env vars vanished"
to the person who thought that file was hand-maintained. Both now
prefer ~/.config/panama/env, the migrated home outside the checkout,
with the repo path kept only as a read fallback for unmigrated
machines. The stale legacy copy on this machine is retired; the
working credentials were merged into the migrated file first, after
probing both sets against the live Home Assistant.
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
Desktop & Dock becomes Shell — Bar, Dock, Control Center, Tiling,
Workspaces — the home for everything Quickshell draws. The settings-
management cluster moves to System as Sync & Backup, Appearance's
Shell tab dissolves, and 24-hour time finally lives on Date & Time,
which always owned it.
The bar gets what it never had: a way to survive the wallpaper. A
second neutral text family (follow theme, or forced light or dark),
a one-layer shadow under every glyph, and a gradient scrim for
wallpapers nothing else survives — all off by default, pixel-identical
until asked. Widgets earn toggles (weather, media, clipboard, calendar
countdown), the vitals cluster stops leaving a dead pill behind, and
Control Center's sections learn to step aside.
The dock graduates from MVP: a context menu with window rows, pin,
unpin, quit and new-window; scroll an icon to cycle its windows; drag
to reorder on the dock itself; hover previews with one-shot captures;
and "Add App to Dock" in the launcher. Three real bugs died en route —
menus that slid away with the autohide, a readonly-property crash on
every menu open, and a drag that drifted half a slot per icon on side
docks. The pinned-apps editor in Settings becomes a drag strip.
166 contracts; the full suite is green except two live display and
switcher tests that cannot run behind a locked session — re-verified
on unlock.
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
Shipping met reality tonight, and reality won three rounds before we
did. mpvpaper's -f forks it into the background, which a supervisor
reads as instant death — the respawn loop repainted the desktop black
once a second. The reaper's own kills fired onExited like crashes, so
the supervisor ate its young until deliberate deaths got marked. And
the video's path shared wallpaperPath with the still pipeline, whose
transactional persistence clobbered it — it now lives under its own
videoWallpaperPath key, read by the lock screen and the doctor too.
Restore is the service's own now, reactive and once per session: a
one-shot timer raced the async preference load and availability probe
at cold start and silently lost. A video-wallpaper IPC target drives
start/stop/pause from a terminal and from the contract sweep to come.
Verified live: one player per output, same PID across restarts, VAAPI
engaged, 0.2% CPU steady for 1440p30 h264, pause from the bar pill.
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
Appearance now opens on Themes: light and dark side by side, each
remembering its own choice, over galleries of ten shipped themes —
Tokyo Moon and Day joined by Moon Rose, Catppuccin, Nord, Gruvbox and
Everforest in both modes. A theme is a complete palette: the catalog
lives in themes.json, Theme.qml reads every color token from the
active record, and one render pipeline carries it to kitty, tmux,
btop, GTK, Vicinae, Firefox's chrome, and the lock screen. The Theme
editor builds new ones from four wells — wheel, hex, or eyedropper —
with derived surfaces, a saturation slider, debounced fine-tune, and
effects that save with the theme. Custom edits finally keep GNOME's
accent, kitty's border, and hyprlock in sync.
Wallpapers can be video: mpvpaper per output, hardware-decoded, muted
and looped, supervised and respawned. Panama owns the pausing — games,
battery, and a bar pill for right now — because the compositor
rebuilds full-screen blur for every frame a video wallpaper draws.
The lock screen gets a still frame.
Titlebars stop lying. GNOME apps get close-only on your chosen side,
the maximize and double-click settings are gone, the Settings window
obeys the same rules, and its titlebar can be turned off entirely.
Typography becomes five labeled dropdowns instead of a wall of
samples.
Contracts updated and written throughout (165 now); per the redesign
workflow none were executed — the full sweep runs once at the end.
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
Home is now Overview | My Home | Phone. Overview leads with quick-action
tiles (focus, Do Not Disturb, health, snapshots, storage), keeps the
findings card — updates fold in, the reclaim-space prompt is gone on
purpose — and adds glance cards, the next calendar event, and weather.
My Home groups every light by Home Assistant area: the helper gained an
`areas` command (one REST template render, no websocket), and the rooms
degrade to a flat list on setups without areas. The favorites editor and
connection card moved intact. Phone gains a vitals strip — battery and
cell signal read from KDE Connect's plugin D-Bus objects, where absence
is data, not an error — beside ring, clipboard, send-a-file, and the
BlueBubbles handoff.
The retired home-phone id resolves to my-home forever via a new alias
map in SettingsRoutes (with a hasOwnProperty guard so prototype names
cannot leak into settingsPage). Storage no longer claims 0 B free — the
old page read a field the disks helper never emitted.
Contracts updated alongside; per the new workflow, the full suite runs
once at the end of the redesign (see the test backlog note).
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
The sidebar was a flat scan of thirty-one rows; now it reads like a
settings app. Multi-subject categories (Input, Network & Sharing,
Applications, Users & Accounts, Privacy & Security, System) carry an
Appearance-style tab strip above the page, drawn by the shell so the
leaf pages themselves are untouched. The taxonomy lives in one new
file, services/SettingsRoutes.qml; the sidebar, the strip, route
validation, search breadcrumbs, and both generators derive from it.
ShellState.settingsPage still holds leaf ids, so every deep link, IPC
call, and search result keeps working — and now lands on the exact
tab. Dictation moves out of Sound onto its own page under Input, with
a handoff back to Sound for the microphone. The strip scrolls when
System's nine tabs outgrow a tiled window. All 161 contracts pass.
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
A sweep for controls wired to targets the backend does not handle -- the shape
of the dictation bug -- found one more: "Start Orca" on the Accessibility page
called openApplication("orca") with no "orca" in the command map, so it set an
error and launched nothing. orca ships in hyprland-packages; it now runs.
That is the whole count. Every service-method call across 84 settings pages
resolves, every helper subcommand a service invokes is implemented, every
openGnomePanel handoff is allow-listed. Two dead buttons existed in the entire
settings app -- the dictation Download and this -- and both are fixed.
settings-buttons-contract pins the class: every openApplication id and
openGnomePanel panel a QML button passes must be present in SystemSettings'
dispatch maps, both of which return false silently on an unknown name.
Verified it catches the orca button when the fix is reverted.
And a dependency-contract exception the ffmpeg fix needed: ffmpeg is provided
by a swap (ffmpeg-free -> ffmpeg), never a list entry, because listing it is
the conflict that fix removed -- so panama-transcode's use of it is satisfied
without a package name to point at.
Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh
The Download button on Sound → Dictation ran `panama-dictate download`. There
is no such command: the helper answered {"ok": false, "error":
"unknown-command"} and the button did nothing, every time, for anyone who
tried it. The real command is `setup`, which pulls the speech server image
and downloads the ~490 MB model together -- one trip, by design. And the card
told you the server needed `panama app whisper-vulkan`, an app that exists in
no package or setup/apps entry; the server is a prebuilt image the helper
pulls with podman.
So the card described three things and got all three wrong. It now shows the
real state of both pieces and offers one action -- "Set up dictation" -- wired
to the command that actually fetches them, with progress that names which half
is running (server image, then model). Verified end to end: `panama-dictate
setup` emits the pulling/downloading JSON the service parses.
Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh