aboutsummaryrefslogtreecommitdiffstats
path: root/desktop
AgeCommit message (Collapse)AuthorFilesLines
14 hoursvm: say what to do when a saved state won't restoreHEADmasterDanilo M.1-1/+9
A managed save written by libvirt 12.0.0's default raw format restores through an fd: migration, which QEMU 11.1.2 refuses with "migrate-incoming: fd: migration to a file is not supported. Use file: instead." The start then fails the same way every time, and the notification only carried that raw error, which does not say the fix is the page's own Discard saved state button. A start failure mentioning migrate-incoming now leads with that advice and keeps the raw error below it. The host side was fixed separately with save_image_format = "sparse" in qemu.conf, which restores through file: and was checked with a managedsave and start round trip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
15 hourswallpaper: keep the pair the engine put upDanilo M.3-1/+25
The engine's ticks are light runs: udt-accent leaves GRUB, the homepage and SDDM on the home pair. When a tick lands on a combination worth keeping, there was no way to promote it short of re-picking both images by hand. "Keep this pair" reruns wallp on the current pair without UDT_ROTATION, so the full run brings those three along, and makes it the engine's home, so switching the engine off does not undo it. The timer restarts, as after a pick by hand. The button shows only while the engine is on and the pair up differs from home, so it hides once kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 hoursmonitors: keep unsaved edits across another page's reloadDanilo M.1-1/+4
Any configreloaded reread the draft, so saving on the Options or Keyboard shortcuts page silently dropped unsaved monitor edits. The handler now goes through refresh(), which skips a dirty draft, and revert() returns the draft to orig itself so its own reload still rereads. The spec now says one eval per monitor and the reload after a refusal, as built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 hoursdocs: the monitors settings pageDanilo M.1-0/+36
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 hoursmonitors: keep the drag, freeze the cards while pendingDanilo M.1-1/+10
The page's ScrollView took a vertical drag and cancelled the MouseArea, leaving the monitor drawn at its offset with nothing written; it now prevents stealing and clears the offset on cancel. A click without movement no longer snaps. The cards are disabled while an Apply waits for Keep, which writes the draft and so must not write untried edits. The mode list comes from orig, which set() leaves alone, so an edit to another field does not hand the ComboBox a new model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 hoursmonitors: the layout canvas and the per-monitor cardsDanilo M.1-3/+247
Monitors drawn at logical size, fitted to the canvas; a drag moves one by an offset and on release snaps it within 64 logical px of the other's edges and writes an exact position. The cards set mode from the deduplicated list, scale (read on every change, since a click on Apply does not end editing), rotation and flip, VRR, and auto or exact position. Leaving the page with an Apply unanswered reverts it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 hoursmonitors: report Keep's write, resync without the autoreloadDanilo M.1-2/+8
"saved" now waits for FileView's saved, and a failed write shows its error: the change is live but the next reload would bring the old file back. Keep sets orig from the draft itself rather than relying on the autoreload's read, apply() clears a skipRead a refusal's reload never consumed, and an unreadable hyprctl reply says so instead of showing an empty page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 hoursmonitors: settings module, try live then keep or revertDanilo M.3-0/+182
Apply evals each monitor's hl.monitor line, one call each because eval wraps its argument in a return. Keep writes sections/monitors.lua whole; Revert or 15s without an answer is hyprctl reload, which reapplies the untouched file. A refusal also reloads, since an earlier monitor's eval may have gone through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 hoursmonitors: draw a scale being typed at 1Danilo M.2-2/+13
The page binds the canvas to size(), and an empty or partial scale gave Infinity or NaN and broke the fit. Apply is still held back until the scale is a positive number. Also marks the per-axis snap as a ceiling and checks a rotated scaled monitor and a two-monitor file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 hoursmonitors: pure helpers for the settings pageDanilo M.2-0/+170
The Lua for a monitor and for monitors.lua, logical size, edge snapping and the mode list, with the duplicates the ASUS reports collapsed and the current mode matched by nearest refresh (99.943 against 99.94). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
29 hourssettings: show each monitor on the landing pageDanilo M.1-1/+20
One section per monitor from hyprctl monitors -j, read in the same pass as the system facts: make, model, resolution, refresh, scale, rotation, diagonal and position. The description is left out because it carries the panel's serial number. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
31 hoursai: drop local chatDanilo M.4-30/+9
local-chat (llamachat) is archived and uninstalled, superseded by desktop-assistant, so its row could only ever read stopped and its buttons called a command that no longer exists. Also folds the control comment that described llamachat's restart dance down to one line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
31 hoursDrop breaktimer and the archived AI appsDanilo M.10-209/+21
breaktimer is parked and imggen and fanfictioner are archived and uninstalled, so the shell was reading files nothing writes and offering buttons that call commands that no longer exist. Status: remove the bt* properties, the file watchers behind them, the verb runner and the presentation-mode pause/resume. The status page loses BreakRow and the tile falls back to "All clear". AI: remove the imggen and fanfictioner rows from ai-state.sh, their labels and controls, and the pid-based fanfictioner stop. The sd-cli engine row stays, since the wallpaper upscaler uses sd-cli too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 dayswallpaper: upscale a too-small staged image in placeDanilo M.3-0/+147
A staged wallpaper smaller than its screen (yellow or red border) gets an "Upscale 4x" button. It rewrites the file with Real-ESRGAN through stable-diffusion.cpp's `sd-cli -M upscale`, keeping the name, so wallp's state and the engine's lists, which name it by path, still find it. sd-cli writes to a dot-named sibling the scan skips, and only a successful run is moved over the original. Success needs no [E] line as well as exit 0: with a model that fails to load, sd-cli logs "[E] new_upscaler_ctx failed", saves the input unscaled and exits 0 (measured). The button takes two clicks so the model choice shows before anything is rewritten. The first runs Imajev-4B, a small Jev-style vision decision model, through llama-mtmd-cli on a 512 px thumbnail, with a grammar that allows only A (anime) or B (photo or render); greedy decoding then returns the higher-logit option, about 1.7 s. The label becomes "Upscale 4x (anime)" or "(photo)", and the second click upscales with the anime or the general model. Anything but A, including a failed decider, picks the general model, safe on any image. Imajev-4B was chosen by measurement on 22 wallpapers: all 12 anime and 4 photos right, 4 of 6 painted 3D renders called anime (those come out smoother than painted). The 2B got 17 of 22, leaning to photo; a three-way prompt adding "painted or render" dropped both to 15 or fewer, with most anime called painted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 daysdocs: the wallpaper engine and the burnDanilo M.1-3/+51
The appearance README gains the burn (the cover, the covered() handshake and why, the shader cache trap) and the engine (sources, shuffle, light run, homes, the draft page, where its state lives and what starts it). The spec records what changed while building it: Prefs instead of list files, a page of its own, folder sources, the header buttons, the cover handshake. AGENTS.md gains the Field.edited trap, which applies to every page with a field and a button: a click does not end editing, so the value a button acts on must be read on every change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 dayswallpaper: keep the burn's cover clear until both pictures are decodedDanilo M.1-0/+6
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>
2 dayswallpaper: start wallp only once the burn's cover is on screenDanilo M.2-4/+40
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>
2 dayswallpaper: engine page commits like the others, Start changes at onceDanilo M.3-18/+100
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>
2 dayswallpaper: a screen can draw from a gallery folder instead of its heartsDanilo M.3-7/+48
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>
2 dayswallpaper: engine page glyphs as escapes, like the rest of the treeDanilo M.2-2/+2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 dayswallpaper: the engine's settings pageDanilo M.3-0/+179
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>
2 dayswallpaper: the heart is back, for the engine's per-screen listsDanilo M.1-0/+22
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>
2 dayswallpaper: the engine, a timer over the hearted lists with a home per screenDanilo M.1-0/+115
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>
2 dayswallpaper: one run() for Apply and the engine, with a finished signalDanilo M.2-11/+39
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>
2 daysprefs: the wallpaper engine's settings, lists and homesDanilo M.1-1/+22
The engine needs on/off, an interval, per-screen lists and a home per screen to survive restarts. They live in prefs rather than list files because the lists can be empty, and an empty FileView write writes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 dayswallpaper: a shuffle bag for the engine's orderDanilo M.2-0/+80
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>
3 dayswallpaper: burn the old picture away on ApplyDanilo M.5-0/+183
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>
3 dayssound: drop the OSD's canvas grain, it broke Qt's garbage collectorDanilo M.3-34/+10
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>
3 dayscompanion: remove from the tree, archived as tag archive/companionDanilo M.20-2471/+3
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>
3 daysfonts: one font size for the desktop, with per-system overridesarchive/companionDanilo M.7-0/+663
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>
3 dayssettings: sunset, idle and autostart under the Hyprland groupDanilo M.3-3/+3
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>
3 dayshyprland: keyboard shortcuts and options pages in the settings windowDanilo M.11-0/+1459
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>
4 daysshells: a settings page that restarts the shells, with a notificationDanilo M.5-0/+210
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>
4 daysperf: sum VRAM in a shell script instead of in QMLDanilo M.5-46/+98
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>
4 daysassistant: unload the resident model from the settings pageDanilo M.2-4/+18
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>
4 daysai: unload llama's model from the engine rowDanilo M.3-3/+15
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>
4 daysperf: hold the process list at five rowsDanilo M.1-29/+41
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>
4 daysnetwork: traffic, its busiest processes and the IPs on the pageDanilo M.6-82/+291
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>
6 dayscompanion: the seeds kept for the last seated framesDanilo M.1-0/+4
Seed 42 drew a third hand, the new one plus both still on the bench, on every dress frame among sit-think, sit-listen and sit-wave, and on leotard/sit-think. The comments name the seeds that replaced it so a remake does not have to find them again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 dayscompanion: seated recipes that move her, and no ledgeDanilo M.1-11/+21
Naming a ledge in SIT drew a grey bar across the whole image, so SIT now names nothing under her. Its "keep her sitting pose exactly" also froze her arms, so the posed seated frames do without it: sit-point came out as the plain sit with a smirk, and at seed 42 kept her old hand on her hip too (seed 7 kept). dress/sit-read is hand made, a masked inpaint recorded in the art's recipe.txt, since no edit would bend her forward, and leotard/sit-read names the lowered gaze that ${LEOTARD} turned to the viewer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 dayscompanion: fade the new frame in over the old one, dj loop fasterDanilo M.2-5/+12
Both images faded at once, so mid-swap each sat near half opacity and she blinked see-through on every turn of the dj loop. The incoming frame now fades in on top while the outgoing one stays opaque under it. With the blink gone the loop could run faster: 300 ms a frame, from 450. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysdocs: the companion's seat on the assistant cardDanilo M.1-0/+7
Records that Hyprland's typing binds fire under the canvas's exclusive keyboard, which is why the canvas reports no typing of its own, and that the seat fixes her right edge rather than her centre. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 dayscompanion: the seated frames' recipesDanilo M.1-0/+19
Edited from base-sitting, the original sitting rest pose dress/base came from, so the legs already dangle and klein only changes the arms and expression. The leotard set follows its dress frames as before. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
8 dayscompanion: no move while a hop is in flightDanilo M.1-1/+3
hopping counts as away, so a goal change during the exit (the chat card closing mid-drop) moved the window before the drop had played. settled already takes the latest goal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 dayscompanion: sit on the assistant's chat cardDanilo M.1-13/+37
While the canvas shows its tall card she hops onto its top edge, through the monitor hop: the exit plays, the window's margins move, the entrance plays. The tall card's top is fixed, so nothing follows a moving edge. Seated she ignores focus changes and balloons, and shows Mood.seatFrame of whatever she would show standing. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8 daysassistant: drop Card.panel, its only reader was the old tallDanilo M.1-1/+0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysassistant: the canvas owns tall and the card's topDanilo M.2-2/+8
The companion needs both to sit on the chat card, and should not reach into the card for them. Card.tall now reads the canvas's, so there is one definition of when the card is tall. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8 dayscompanion: seatFrame, the sitting version of a frameDanilo M.2-1/+23
Pure, so the seat's frame choice is tested with the rest of Mood rather than by eye. Frames without a sitting version fall to the plain sit: a standing frame on the card's edge would float above it. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
8 daysperf: the five heaviest processes in the CPU card, by CPU or RAMDanilo M.4-1/+135
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>
8 daysisland: release the tray-menu hold when the menu closesDanilo M.3-8/+24
A tray menu un-hovers the pills, so the island holds itself shown while one is open. display() reports no close, so the hold ended only when the pointer came back to the island. Quitting an app from its menu left the pointer on the vanished menu, below the pills, and the island stayed open until the next hover. Menus now open through a QsMenuAnchor. Its closed signal fires on any dismissal, an entry picked, Escape or a click elsewhere, and clears the hold and restarts the hide timer. Checked by eye: menu placement unchanged, Quit and a dismissal both retract the island, hovering back keeps it shown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>