| Age | Commit message (Collapse) | Author | Files | Lines |
|
A brief flicker was left at the start of the burn on both screens. The
cover drew each picture as soon as it decoded, and the new one, under
the old, usually decodes first, so it showed for a frame or two before
the old one covered it. Both now stay hidden until both are ready; the
surface is clear until then, and swaybg underneath is still showing
the old picture, since wallp waits for covered().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The new wallpaper flashed in for a moment, the old one came back over
it, and only then did it burn. run() started wallp in the same turn as
swapping, while the cover was still mapping and decoding its two
pictures asynchronously, so swaybg swapped under an empty cover.
run() now holds wallp until each cover reports it is drawing (its
pictures decoded, then 50ms for a few frames), with a 1.5s fallback for
a cover that never comes: a missing screen or a picture that fails to
load. A run with nothing to cover starts at once, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The page was the only settings page without the header's buttons, and
it switched the engine from a checkbox beside the interval. Both looked
like the engine had been started when it had not:
- The minutes field saved on editingFinished, which needs Enter or a
focus change. Clicking the checkbox takes neither, so a typed 1 was
left at 30 and the first change was half an hour away.
- Even at the right interval, the first change waited one full
interval, so starting the engine did nothing visible.
The interval and the two sources are now a draft in WallEngine, updated
on every keystroke, and the header carries the state ("running, next
change at hh:mm", "stopped", "unsaved changes") with Reset, Stop and
Start, which reads Apply while running. Start commits the draft, sets
the homes and changes the wallpapers straight away; Apply commits and
restarts the timer. Hearts stay undrafted, written as clicked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Every wallpaper in the gallery is wide, so hearting a whole folder one
thumbnail at a time is the long way to say "this folder". Each screen
now has a source on the engine page: its hearted list, or one gallery
folder picked by chip, stored as the Wallpapers.groups key in Prefs
(folderH, folderV, "" for the hearts).
A folder that has left the gallery gives an empty pool, so that screen
is left alone rather than quietly falling back to its hearts. The
hearted list stays saved behind a folder source and comes back with the
Hearted chip. Switching source needs nothing from the shuffle: draw()
already drops bag entries that are no longer in the pool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A page for the wallpaper engine: the on switch, the interval in
minutes, a Next button, and each screen's hearted list with a remove
control and a dim for files the gallery's scan did not find. The module
reads WallEngine.on itself, because a singleton is built on first use
and the timer has to run from session start, page opened or not.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A heart on each gallery thumbnail adds or removes the image from the
engine's list for the screen the picker targets. It is the last child of
the thumbnail, so it sits above the pick MouseArea and takes its own
click, and it is outlined so it reads on a light image.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Ticks go through Wallpapers.run as the light run so the burn plays without
touching GRUB, the homepage or SDDM. Switching off returns each screen to
its home with a full run, deferred until any run in flight finishes. Not
yet registered in the shell; that comes with the settings page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The engine's timer must set wallpapers the same way Apply does so the
burn plays. run() is that one path; applied stays Apply's alone (save bar,
window close) while finished marks every run's end, which is what the
spread overlay now hides on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Each round shows every hearted wallpaper once, in random order, so no
picture is skipped or doubled within a round. A reshuffle never opens
with the picture on screen, so the same wallpaper cannot appear twice
in a row across the round boundary.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
|
|
swaybg has no transitions: wallp kills the running one and starts a new
one, which can show a frame of nothing in between. WallpaperSpread puts a
click-through surface on the bottom layer of each screen, above swaybg and
below every window, holding the old picture while wallp runs. A shader
burns it away over the new one: each pixel's threshold mixes its distance
from a random starting point with fractal noise, and a flame and char band
runs just ahead of the front. The surface goes once wallp has exited, or
at once if it failed, since a failed set may have left either picture.
Wallpapers.apply() announces each swap with the old path before wallp
runs, while current still names it. Sets from a terminal get no effect:
by the time wall_h changes, swaybg has already swapped.
The animation is linear, 3.5s: an ease spent its slow ends where nothing
was left to burn, and read as well under the 2.6s it was given. The
compiled .qsb is committed beside its source, since nothing builds it.
AGENTS.md gains the shader cache trap hit on the way: a running shell
keeps a shader by URL, so a recompiled .qsb under the same name is
ignored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The desktop shell's recurring 100% CPU spin is a Qt bug set off by the
volume OSD. Its grain Canvas called createImageData on every paint, and
Qt 6.11 subtracts that pixel buffer from the V4 unmanaged-heap counter
without having added it. The first GC after an OSD show wrapped the
size_t counter, and from then on every JS allocation ran a full GC until
a restart. The OSD shows on volume keys, track changes and resume, which
is why the spin followed all three.
Found by running each always-on unit of the desktop in its own qs with
GC stats: only the sound module wrapped, 3 s after a resume, and a
volume nudge reproduced it on demand. Qt's qml tool with a bare Canvas
confirms it is createImageData alone: 17,856 of 17,856 GCs wrapped, the
same canvas drawing with fillRect 0 of 5. With the grain removed, 8 OSD
shows under forced GCs left the counter intact.
The grain was alpha 14/255, so the OSD looks nearly the same. vram.sh's
comment blamed the fdinfo parse for the underflow; corrected, and the
lesson is in AGENTS.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The chibi 2B companion is retired for now. Its last state is kept in the
signed tag archive/companion (the commit before this one), so it can be
revisited with `git checkout archive/companion`; hypr-theme carries a tag
of the same name for its compositor hooks (typing/delete events and the
SUPER+\ bind).
Removed with it, since nothing else used them: the Companion, phrases
and artists settings pages, the five companion prefs in Prefs.qml and
Prefs.ready, the wallpaper heart (Admired.qml's list), the assistant's
replied() signal and cardTop, and the keybinds reminder's name for the
code:49 bind. The design specs and plans under docs/ stay as history.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A settings page under Appearance > Fonts holds one pixel size for the
whole desktop and an override wherever a system needs its own, saved as
~/.config/keystone/fonts.json, all in px. At scale 1 the logical DPI is
96, so a point is 4/3 px, and each system is written in its own unit:
Theme.fontSize reads the json itself, so every shell follows live;
qt5ct/qt6ct and the GTK settings files are edited in place, size only,
plus gsettings; kitty, rofi and hyprlock read generated files under
~/.config/keystone that their configs include over designed fallbacks;
the groupbar is a keystone.lua line.
rofi and hyprlock are sections of designed sizes that scale together
from their body text, the smallest entry, each keeping its ratio unless
given its own size. The design is read from the fallbacks themselves,
var(kf-..., "Family N") in the rofi files and $kf_... = N in
hyprlock.conf, so a new entry appears on the page with no list to keep.
With no fonts.json the page starts from what the files say, base 16 and
an override per system at its current size, so the first save changes
nothing; seeded to 0.01px, since 0.1 scaled rofi's 72pt glyph to 72.2.
Each stepper shows px then pt in fixed columns, the system's own unit
highlighted.
Check: node desktop/modules/fonts/test_fonts.js
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
All three edit Hyprland's own config or its companion daemons, so they
sit with the shortcuts and options pages rather than under Desktop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Keyboard shortcuts lists every bind hyprctl reports, joined to the
hl.bind call in ~/.config/hypr that made it: hyprctl gives every Lua
action as an opaque __lua number, so the action and file:line only
exist in the source. The join is by description, then combo, then the
longest literal head of a description built in a loop. List, Source
and Clashes tabs. A combo the source writes as a plain string (or from
file-level string locals) can be changed: hyprctl eval of a do-nothing
bind checks it first, refusing an unknown modifier or keysym, then the
call's first argument is rewritten and Hyprland autoreloads.
Options lists all 353 options from hyprctl descriptions, with values
from one batched getoption, since descriptions' own "current" field is
stale. Filters for everything, set in the config, and changed from the
default. A change is one line in hypr-theme's sections/keystone.lua,
required last, and is run through hyprctl eval first, which refuses an
unknown key or a wrongly shaped value without changing anything.
Both modules keep their own copy of the file text: FileView.text()
returns the old text right after setText(), and a second quick edit
built from it dropped the first. The AGENTS.md notes record that and
the hyprctl quirks found on the way.
Check: node desktop/modules/hyprland/test_hypr.js
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Settings > Desktop > Shells lists every directory holding a shell.qml,
running or stopped by `qs list --all`, with Restart (Start when it is not
running) per row and a Restart all. Every button runs reload.sh through
Quickshell.execDetached, which leaves the script outside the desktop
shell's process, so restarting desktop from its own settings window
works: the window closes and the new process carries on.
restart.sh wraps reload.sh and sends one notification naming the shells
that came back, or a critical one naming those that did not. Back means
qs lists an instance on the shell's config path under a pid it did not
have before, within 5s; the old instance can linger in the list for a
moment after qs kill. It checks the process, not the config, so a shell
that starts with a broken component still counts as back.
The rows reuse the ai module's AiRow rather than a copy of it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A process holding thousands of DRM fds makes the fdinfo scan a few
hundred KB. Parsing that in QML every 2s drove Qt's unmanaged-heap
counter to underflow, after which the V4 engine ran a GC on every
allocation and the shell sat at 100% CPU.
vram.sh does the scan and the per-client-id sum in awk and prints one
number, or nothing when no xe client is found, which the panel draws as
a dash. Perf.js loses vram() and its test; test_vram.sh carries the same
cases against the script, which takes a file argument instead of
scanning so the test can feed it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The Load button reads Unload on the loaded model and posts to the
router's /models/unload. An error from the router lands in modelError,
the same place a failed load reports, and the list is re-read either way.
The model comes back on the next request for it or when the canvas opens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The llama.cpp row offers Unload while a model is loaded. It posts to the
router's /models/unload, which frees the VRAM and leaves llama-server up,
so its web UI stays available; the engines themselves are still never
stopped from here. The answer is not read: the next poll shows the row
without a model, or with it if the unload failed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The list fills about 2s after the page opens and can show fewer than five
rows, which resized the CPU card and shifted every card below it. A
FontMetrics-sized Column keeps the height fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The rx/tx graph, the LAN address and the public IP with its pin move from
the perf page's NET card to the network page, where they belong; the NET
card is gone. Perf still samples and owns all of them: the graph opens on
the last minute of history, and a public IP that differs from the pin
still lights the perf tile's dot.
Below the graph, the five busiest processes. nethogs is installed but
cannot open any interface unprivileged, so the rates come from
`ss -tinpH`: per-socket byte counters diffed every 2s while the page is
open, summed by process name. That counts TCP only, and only the user's
own processes, so QUIC and system services are missing. The list is held
at five rows tall so the IP lines under it never move.
Perf.js gains sockets() and netProcs(), with tests in test_perf.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A table under RAM lists the top five processes with their CPU and
resident memory, sorted by either through a toggle in the card. The
toggle re-sorts the frame already held and is not saved.
The rows come from one `top -b -d2` that runs only while the page is
open and dies with it, checked with pgrep. ps was not used: its %CPU is
a lifetime average. top's first frame is the same lifetime figure, so it
is skipped. top marks no frame end but writes each frame in one burst, so
a frame is published once its output has been quiet for 150ms, and rows
appear about 2s after opening, not 4s as when waiting for the next
header.
SplitParser delivered every header after the first as "\ntop - ...", so
the header match is a regex that allows leading whitespace; AGENTS.md
records it. The parser is Perf.js procs(), covered in test_perf.js.
Columns are right-aligned at fixed widths with tabular figures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
PhrasesPage takes its intro, key hint, add-button text and saved set, so
the artist set reuses it rather than a copy that would drift. Reset's
enabled state read Prefs.companionApps directly and would have tracked the
wrong set. ArtistsModule mirrors PhrasesModule on Prefs.companionArtists
and Lines.artists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Her per-app lines were hardcoded in Lines.js under app:<class>. They now live in Prefs.companionApps once edited and fall back to Lines.js until then, read live by Companion.qml so a save needs no restart. The new Companion phrases page does full CRUD on apps and their phrases, with a draft, SaveBar and Reset to the built-in set, mirroring Ask presets. Companion settings move out of Desktop into their own Companion group.
|
|
The Prefer rows for cloud models never built: the shell's JS engine has
no Array.prototype.flatMap, and the Repeater's model threw a TypeError
(seen in qs log). The ids are now joined with reduce in the module.
Each tool row gains a cloud switch, applied on Save after the renames:
it writes or drops a top-level cloud = true, validated with tomllib and
replaced atomically, on name.toml or name.toml.off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The canvas asks for its chat's model, else the preferred one, when it
opens and when the pick changes; a cloud id loads nothing. The paperclip
also closes the model panel, so it and the picker never stack. The plan's
Models.qml import is corrected to the one that resolves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
Settings > Desktop > Companion takes the seconds she stays in the
leotard after the self-destruct, 60 as before. Switched off, she keeps
it until she is hidden and shown again, which already put the dress
back early. Mood.outfit takes the time instead of reading T.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
At the label's size it was hard to spot. An outline in the base colour
keeps it readable on a light image.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Shown on hover, and always on a listed one. It toggles the path in the
list file and leaves the rest of the file as it was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Empty means the default under ~/.config/keystone, shown as the
placeholder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
SUPER+\ only flipped a property, so every restart of the desktop shell
brought her back. Whether she is shown is now `companion` in Prefs.qml,
set by the IPC and by a switch on a new Settings > Desktop > Companion
page. Prefs gains `ready`, so she waits for the file rather than showing
at the default and then vanishing; the greeting now plays on every
appearance, the one at login included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A Restart button sits before Stop on a running app that has a restart
control. The assistant uses assistant.sh restart. llamachat has none,
and its --quit answers before the app has exited, so a --daemon sent
straight after finds an instance still running and does nothing: the
restart quits, waits for --ping to fail (5s at most), then starts it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The name and size were one string, so sizes started wherever the name
ended and a column of them could not be scanned. The size is now its own
right-anchored Text in the main text colour, and a long name elides in the
middle instead of pushing it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Update runs `docker compose -p <project> -f <files> up -d --pull always`
with the files read from the com.docker.compose.project.config_files label,
so only stacks compose itself made get the button: a plain `docker run`
container has no recipe to recreate it from, and the supabase CLI labels
the project and nothing else. It has its own Process because a pull runs
for minutes and must not be clobbered by a start or stop meanwhile.
Published TCP ports become localhost:<port> buttons opened by the default
browser. Docker cannot say which port is a web UI, so every published one
gets a button and a port ending in 443 is guessed https.
Prune images runs `docker image prune -a -f` behind a confirm step, since a
re-pull can be gigabytes. The images it would delete are listed under the
header from `docker image ls`'s {{.Containers}} count, which docker 29
fills in; images of stopped containers are kept. That count reads 0 for
images only used by `--rm` containers, such as the test-build ones, so the
list is the warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The Theme page applied a scheme on click, so a stray click reinstalled the
whole theme, and it was the one settings page without the header's
commit point. Clicking now stages the scheme (outlined yellow, "selected"),
and Reset / Apply in the header drop or apply it, as the wallpaper page
does. The applied notice and any failure moved into the SaveBar, and the
sidebar marks the page dirty while a scheme is staged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Shows the live scheme, accent and both wallpapers, the scheme and
wallpapers linking to their own pages, then every udt consumer in a
software and a hardware list, each with a Re-apply button running
`install.sh <name>`, and Re-apply all in the header. The list comes from
`install.sh --list`, so a consumer added in unified-desktop-theme shows up
here with no edit. One run at a time, and none during a scheme switch.
QML's JS engine has no Object.fromEntries either: it threw a TypeError
that only reached the log and left the page without its list. Noted in
AGENTS.md beside String.matchAll.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Docker.qml mirrors Virsh: the docker CLI, an event stream that runs for the
whole session, and `docker stats --no-stream` on the 2s timer only while the
page is up. Containers are grouped by compose project, since a stack is a
dozen containers that start and stop together; a container outside any
project is a group of one. Verbs are start, stop, restart and resume, by
container id, so no compose file is needed and nothing removes a container.
The event stream is filtered to lifecycle events: health checks emit exec_*
events every few seconds per container, which would make the stream a poll.
It dies with the daemon, so it is retried every 10s.
An inactive Loader keeps its last height and a Column still reserves it, so
collapsing one card left an empty box where its detail had been. Both the
container and VM detail loaders now hide while inactive; the VM one had the
same bug, unseen with a single VM.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The helper replaces its default [manifests] table with the file's, so
adding one manifest to a file without the table dropped neovim's, and
the page showed no rows for the default that was in effect. With no
table in the file the page now lists the helper's defaults, and the
first add, remove or URL edit writes them all before its own change.
Toml.js gains sections(), to tell a missing table from an empty one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The shared Switch flipped its own checked on click, which replaced the
caller's binding with a constant. Every use binds checked, so after a
click the switch stopped following its source: Discard on a settings
draft reverted the value and left the switch showing the click. Four
status rows and the headphones panel each carried a resync workaround
for it.
toggled now carries the new state and the switch moves only when the
caller's binding does. Handlers that read checked as the new value take
the argument instead; the workarounds are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Toml: a value followed by anything but a comment was parsed as if the
line ended there, so a multi-line string (which opens with an empty one)
or `x = "a" junk` read as valid and set() could write a duplicate or
mangle it. entries() now skips such lines and set() refuses them with the
existing "cannot edit" error. The ponytail comment names the ceilings.
Store: a failed put left Save enabled with a stale draft; it now marks the
key invalid. An empty manifest URL used to unset, which removed the row
being typed in; it now marks the row invalid instead (Setting clears the
mark just before set, so the one made inside set stays). Noted that list
entries with spaces cannot be entered here.
Sbb: refresh() ran an ssh fetch for the desktop page too and let two
fetches overlap, or one run during a save. It is split into reloadDesktop()
and fetch(false), and fetch() returns while busy. A successful config-get
clears a stale VM error, and a missing text reads as empty. The put now
writes and records the draft as sent, so typing during the save is not
marked saved.
SbbVmModule: fields are read-only while busy, a Reload button sits beside
the status line, and the manifest URL field says Remove deletes the row.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
Under SB Builder in the settings window. The desktop page edits
sb-builder's config.toml in place; VM control is a switch because an
emptied field means the default domain, not "". The VM page is
read-only until the helper answers config-get.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The desktop config is a watched local file; the VM one goes through the
helper's config-get and config-set over ssh, so the helper validates what
lands in root's file. Field text is typed after the key's default, the
same check sb-builder makes on load.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
The sb-builder pages need the same fields over another file. The store
defaults to Qtm, so the Mail pages are unchanged.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
Same shape as qtmaildir's Ini.js, so a file the user annotated keeps its
comments: unset comments a key out, set reactivates it. A value spanning
lines throws instead of gaining a duplicate key, which TOML rejects.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|