aboutsummaryrefslogtreecommitdiffstats
AgeCommit message (Collapse)AuthorFilesLines
2026-09-18fix(status): only pause/resume a running breaktimer daemonDanilo M.1-2/+4
breaktimer.sh's pause and resume arms write the state file whether or not a daemon is running, so statusctl presentation set while the daemon was stopped recorded a phantom paused/running state and the tile then showed Paused or a frozen countdown for a daemon that does not exist. Gate both verbs on btRunning, the singleton's running-or-paused derived state, so a verb is only sent when a daemon actually exists.
2026-09-18docs(status): list stopped in the phase domainDanilo M.1-1/+1
2026-09-18docs(status): document the breaktimer read directionDanilo M.1-3/+41
The one rule worth writing down is which side writes: the daemon owns its files and the shell only calls verbs.
2026-09-18feat(status): show the breaktimer countdown on the tileDanilo M.1-1/+8
Below every mode in the priority chain, so an active mode still owns the line and breaktimer replaces only the idle 'All clear'. It does not count toward activeCount. A running breaktimer is not a mode the user switched on, and accenting the tile for an ordinary working day would read as a mode stuck on.
2026-09-18feat(status): a breaktimer row in the status pageDanilo M.2-0/+104
Into the status module rather than a module of its own: break state is one more thing the desktop is doing, which is what that page already is, and Status.qml was already where breaktimer.sh is called from. The paused state shows no number. The countdown is frozen while paused and a frozen number reads as a bug, so the row says what it is instead.
2026-09-18feat(status): read breaktimer phase, state and countdownDanilo M.1-0/+74
The daemon already publishes these three files under XDG_RUNTIME_DIR and waybar already consumes them, so a second consumer costs three FileViews and invents no interface. Read only, deliberately. The daemon loop rewrites state and phase on every transition, so a second writer would race it, which is why presentation mode has always called a verb rather than writing the file. The countdown does not interpolate between the daemon's five second writes: a local clock would drift against it and keep ticking while the daemon is frozen outside the work window.
2026-09-18docs(status): implementation plan for breaktimer controlDanilo M.1-0/+1259
Nine tasks over two repos. The daemon half gains a sourced config file and a config verb that prints the effective settings, which is what makes the config testable without running a thirty minute work block. The shell half reads the daemon's three runtime files, adds a row and a tile line. Three assumptions were probed rather than trusted while writing it, each with a precedent for biting here: padStart exists in this QML engine (matchAll does not), a Connections resync fires correctly on a singleton property, and the config source line leaves unnamed variables alone. The qs verification steps redirect rather than pipe. Piped output is lost when timeout kills the process, which reads as a silent success, and that failure happened twice while probing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18docs(status): spec breaktimer control and a config fileDanilo M.1-0/+221
The daemon already publishes its phase, state and countdown as files under XDG_RUNTIME_DIR, and the status module already shells out to it for presentation mode. Reading those files makes the traffic two-way for the cost of three FileViews, with no new interface to invent. Into status/ rather than a module of its own: breaktimer state is one more thing the desktop is doing, which is what that page already is, and Status.qml is already where breaktimer.sh is called from. The config file is the other half, and stands alone. Every tunable is a shell variable in a tracked script, so the installed copy and the committed one have already drifted over three sound paths pointing into a home directory. One sourced file fixes that with no parser and no format. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18fix(appearance): drop the live cursor hover previewDanilo M.1-33/+5
Hovering a cursor card called `hyprctl setcursor` to switch the real pointer, and reverted it on unhover. That looked read-only and was not: Hyprland's setcursor handler also writes the theme to org.gnome.desktop.interface cursor-theme, so every hover persisted a setting the user had not confirmed. Closing the panel with the pointer over a card left that theme set, surviving both the shell and the session. A `dconf watch /org/gnome/desktop/interface/` during a hover across three cards showed the key alternating between each hovered theme and the current one, one write per hover and one per revert. `strings` on the Hyprland binary carries both `org.gnome.desktop.interface` and `cursor-theme`; `hyprctl` carries neither, so the write is the compositor's, not the caller's. The revert could not be made reliable either. It ran through `execDetached`, which gives no ordering, so a fast card-to-card move could land the revert after the new set. Guarding it by lifecycle did not help: `Component.onDestruction` fires too late to be certain, and the same spawn race applied to Apply, which cleared the hover before `applyCursor` and so raced the old theme against the new one. None of that is fixable while the preview exists, because the side effect belongs to the compositor. The four rendered shapes on each card were always the real preview; the live switch was a bonus that could not be had without writing the setting. Remove it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18feat(sound): show the active device name in the volume OSDDanilo M.1-2/+12
The OSD showed only "Output"/"Input", not which device was in use, so switching between, say, the analogue output and a USB DAC gave no visible confirmation beyond the volume itself. The name falls back description -> nickname -> name, the same order SoundPage.devLabel uses, and elides. The panel height grows to fit the new line.
2026-09-18feat(status): add a screen lock modeDanilo M.7-14/+201
dnd and presentation were the only modes, and presentation was the sole holder of the Wayland idle inhibitor. That inhibitor is the only thing that stops hypridle's loginctl lock-session at the 600s timeout, so screen locking is on by default and there was no way to turn it off on its own. nolock is stored as "auto-lock disabled" so the file keeps the registry's "missing means off" rule, and the row inverts it so the switch reads as the default: lock on. Presentation now drives nolock the same way it drives dnd, holding the prior value in memory and restoring it on exit, so it needs no second inhibitor and the inhibitor has a single holder. The page sets the disable permanently or for a number of minutes. The deadline and its timer live in the singleton rather than the row because the drawer page is destroyed on close while the effect has to outlive it. The timer is shell-lifetime, the same class of limit as dndBeforePresentation: a restart during a timed disable leaves the lock off until it is toggled, which is safe because the user opted into the disable. The mode governs idle-triggered locking only. The SUPER+l bind and the lock on suspend are separate paths and stay live.
2026-09-17fix(status): read a missing state file as off, not downDanilo M.3-5/+9
statusctl watch reported class "down" for a missing state file and waybar's presentation rule drew a warning for it. That fires on every fresh session, before anything has written a file, which is the normal off state the registry's own invariant describes. The state was meant to flag a dead registry, but a crashed shell leaves a stale present file, not a missing one, so it never caught that case. get already read a missing file as 0; watch now matches it and the down state is gone.
2026-09-16fix(appearance): stage a cursor theme, apply on confirmDanilo M.1-5/+41
Clicking a cursor card called applyCursor straight away, so the pointer, gsettings, environment.lua and both GTK settings files all changed on a single click with nothing to confirm. The wallpaper tab already stages a pick and commits it from an Apply button; the cursor grid now does the same, with a Reset beside it. The hover preview is unchanged: it still switches the real cursor and reverts on unhover, which is what makes staging readable, you see the theme before committing to it. That preview is also why Apply clears liveCursorCursor before calling applyCursor. Moving the mouse off the card to reach the button fires the unhover revert, which would otherwise set the old theme back over the one just applied. A staged card takes the solid accent border and the current one drops to a half-alpha border, so both stay visible at once. Icon themes and cursor size still apply on click. Neither has a hover preview to confirm against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16fix(window-switcher): use a messaging icon for desktop 7Danilo M.1-1/+1
The chat/internet-chat aliases in the icon theme all resolve to an empty outlined monitor; dialog-messages is the filled speech bubble.
2026-09-16fix(window-switcher): swap the desktop 2 and 3 iconsDanilo M.1-2/+2
Desktop 2 is the editor and desktop 3 the terminal; the map had them the other way round, matching waybar's stylesheet before it was corrected.
2026-09-16feat(window-switcher): mark desktops with waybar's iconsDanilo M.3-8/+62
The card's desktop line showed the workspace number. waybar draws the same eight desktops as CSS background images keyed by number, because its format-icons takes text rather than paths; those names now live in the Windows singleton and are resolved through the active icon theme, so a theme switch follows the drawer and the bar and the switcher agree. A named workspace, or one past eight, falls back to its number, and so does an icon the active theme cannot resolve.
2026-09-16feat(appearance): polish the Icons tab previews and applyDanilo M.4-129/+270
Cursor previews were extracted only on hover, so the row stayed blank until each card was visited. They are now batched on open: one python pass extracts four shapes per theme (arrow, hand, text, horizontal resize) through the theme's aliases and every storage form it may use (hyprcursor .hlc, a shape directory SVG, or a legacy Xcursor binary via xcur2png). The card rows are Flows so they wrap instead of running off the right edge, and the tab scrolls. Cards widened and names wrap to two lines. A size selector (16-64) applies the pointer size live, to gsettings, the GTK settings.ini files and environment.lua. gsettings alone does not reliably switch icon/cursor here: both gtk-3.0 and gtk-4.0 settings.ini carry the names, so apply now rewrites gtk-icon-theme-name / gtk-cursor-theme-name / gtk-cursor-theme-size there too. The symlinked `default` cursor alias is skipped in the scan and the current value resolved through it, so the real theme carries the current marker. The tab row is centred against the panel so it reads as drawer chrome rather than the first line of a tab's content.
2026-09-16fix(appearance): wait for hyprsunset exit before restartDanilo M.1-1/+7
pkill only sends SIGTERM, and hyprsunset binds Hyprland's CTM manager exclusively, so the replacement started immediately after pkill raced the old process and exited with 'A CTM manager is already running'. Saving from the Sunset tab therefore killed the night-light daemon instead of restarting it. Bound the wait to 5s (50 x 0.1s) so a wedged process cannot hang the restart.
2026-09-16fix(appearance): final review fixes for the Sunset, Idle and Icons tabsDanilo M.4-7/+31
xcur2png wrote its config file into the qs CWD, which is the repo root, so hovering a legacy Xcursor theme left a stray left_ptr.conf in the tree. Point -c at the per-theme cache dir instead. Hyprsunset.save() could run before refresh()'s two cat processes landed and write a header-only hyprsunset.conf, blanking the location. Guard on the same confLoaded/appLoaded flags Hypridle already uses. The idle save had no acknowledgement, which the spec requires. Add a notice after the restart and show it in the tab, and switch Hypridle's save guard to a loaded flag so a config whose first block is a listener is not mistaken for an unloaded one. classify() used else if, so a directory carrying both index.theme and cursors/ (Adwaita) was listed as a cursor theme only and never reached the icon picker. Make the two tests independent; selftest gains the dual-purpose case.
2026-09-16docs(appearance): fix stale Theme tab phrase and align the tab diagramDanilo M.1-9/+9
The five-tab shell's paragraph still said theme opened "the other tab", and the widened top border left the body and bottom border 6 columns short. Widen lines 7-14 to match and name the Theme tab.
2026-09-16docs(appearance): document the Sunset, Idle and Icons tabsDanilo M.1-1/+27
Records the IPC verbs, the shared hyprsunset.conf format, the preserved hypridle comments, and the preview mechanisms. The hardcoded icon theme in udt, waybar and rofi is tracked as a TODO in those repos.
2026-09-16feat(appearance): add the Icons tabDanilo M.2-0/+185
Icon themes preview four glyphs each from the GTK lookup; cursor themes preview an extracted left_ptr and change the real cursor on hover, reverted on leave and on close. Selection applies both.
2026-09-16fix(appearance): show idle timeouts as m:ssDanilo M.1-1/+1
The spec calls for a seconds field shown as m:ss, but the first pass rendered the brief's '9m 30s' form. Zero-pad the seconds so 570 reads 9:30 and 600 reads 10:00.
2026-09-16feat(appearance): add the Idle tabDanilo M.2-0/+92
Rows for each listener with an enable toggle and a timeout field, the command shown read-only. Save rewrites the file and restarts hypridle.
2026-09-16feat(appearance): add the Sunset tabDanilo M.5-11/+302
Tab bar and Tab-key cycle become five entries, the Loader picks inline or file tabs, and show() refreshes the model. The tab edits the profiles in place, fetches sun times and previews through the daemon.
2026-09-16feat(appearance): add the icon and cursor theme modelDanilo M.2-1/+237
Lists themes from ~/.icons, ~/.local/share/icons and /usr/share/icons, distinguishing cursor themes by cursors/ or manifest.hl. Icon previews come from one GTK lookup process for every theme; cursor previews extract left_ptr from a hyprcursor .hlc via unzip or from an Xcursor theme via xcur2png. Application sets gsettings and the Qt configs, and rewrites the cursor env in environment.lua.
2026-09-16fix(appearance): refuse to save hypridle before the config has loadedDanilo M.1-0/+1
save() could run before refresh()'s async cat completed, when prefix and tail are empty and listeners is empty, so it would write an empty file and restart hypridle with no listeners. The real config always has a general block, so an empty prefix means the load has not happened or failed.
2026-09-16feat(appearance): add the hypridle listener modelDanilo M.2-1/+157
Parses the general block and comments before the first listener and keeps them verbatim; only timeouts and the enabled flag are editable, commands are copied. Disabled listeners are written commented out, and the parser matches the closing brace at line start so the dpms command's inline brace does not truncate the block.
2026-09-16fix(appearance): preserve the raw cache path on settings saveDanilo M.1-2/+4
appConfText() hardcoded path = ~/.config/hyprsunset-qt/sun.json, so any saveSettings() -- from save() or detect() -- silently reset a user-customized cache path while fetchSun() kept writing to the parsed location. Mirror sunset-qt's settings.py: hold the raw unexpanded string in cachePathRaw for serialization and derive the expanded cachePath for file access, so a no-op save is byte-identical and the two stay consistent.
2026-09-16feat(appearance): add the hyprsunset profile modelDanilo M.2-0/+310
Parses and serializes ~/.config/hypr/hyprsunset.conf in the exact format hyprsunset-qt writes, so both editors share the file. Location lives in hyprsunset-qt's own INI; fetching mirrors its curl calls and browser User-Agent. Selftest round-trips a fixture byte-for-byte.
2026-09-16docs: the old waybar notification module is archived, not just deadDanilo M.1-3/+3
The note pointed at waybar/scripts/notifications.py and its module by path. Both moved to ~/bin/archive/ with the rest of the stock waybar theme, whose scripts had no caller left once this shell took over notifications. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16refactor(theme): read the palette from ~/.cache/udtDanilo M.7-8/+8
unified-desktop-theme moved its generated files out of ~/.cache/wal, which pywal used to own and nothing writes any more. The palette udt-accent generates is now at ~/.cache/udt/udt-palette.qml. shared/Theme.qml is the only code change; every component reaches it through a symlink. The module READMEs cite the path, so they move with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15docs: add the appearance sunset/idle/icons implementation planDanilo M.1-0/+1750
Seven tasks: the hyprsunset model, the hypridle model, the icon/cursor model, then the tab bar and one task per tab, finishing with docs and the cross-repo unhardcode TODOs. Models are built and self-tested first so every commit loads. Records the brace-in-command parsing trap and the sunset-qt byte-for-byte serializer shape.
2026-09-15docs: add the appearance sunset/idle/icons designDanilo M.1-0/+214
Design for three new appearance tabs: a hyprsunset-qt-equivalent profile editor, a hypridle timeout/enable editor that preserves the file's comments, and an icon/cursor selector with live previews. Records the preview mechanisms probed on this box (GTK icon lookup, hyprcursor .hlc unzip, xcur2png) and scopes the hardcoded-theme propagation out as follow-ups.
2026-09-15fix(notifications): check every img src and uncache previewDanilo M.2-7/+14
The sanitizer matched the first \bsrc anywhere in the tag, so a data-src="file:///tmp/decoy" before a remote src won the match and kept the whole tag while Qt, which ignores data-src, fetched the remote image. Scan every src assignment in the tag and keep it only if all of them are local. Also set cache: false on the balloon preview, since the daemon overwrites the same path on a replace and QQuickPixmapCache keys on URL, so a replaced notification could show the previous image.
2026-09-15fix(notifications): allowlist local inline image sourcesDanilo M.1-5/+22
The http(s)-only regex let protocol-relative //host, ftp, data and entity-encoded schemes through, and the comment overstated what it removed. Replace it with a deny-by-default allowlist: an <img> is removed unless its src, entities decoded first, is a file: URL or a single leading slash. Protocol-relative //host is rejected while a /absolute/path is kept.
2026-09-15feat(notifications): strip remote inline image sourcesDanilo M.3-2/+10
A notification is untrusted input. Inline <img> now renders only for local sources; an http(s) source is removed before the RichText body is shown, so a remote sender cannot make the shell fetch a URL. The row and the balloon share the one sanitizer in the Notify singleton.
2026-09-15feat(notifications): draw the content image in the balloonDanilo M.1-1/+25
The daemon now publishes an image path; the balloon shows it below the text, scaled to the balloon width with a 240px cap. A daemon without the field leaves it hidden, so the renderer and the daemon can ship in either order.
2026-09-15docs: add the notification images implementation plansDanilo M.2-0/+1218
Two plans, one per subsystem: the notifyd daemon (hint parsing, PNG encode, theme-name resolution, the image field and its cleanup) and the quickshell renderers (the balloon preview and remote inline source stripping). Either can ship first; the renderer tolerates a daemon without the field.
2026-09-15docs: add the notification images designDanilo M.1-0/+201
The daemon drops every image hint today, so a screenshot and an application image never arrive. libnotify 0.8.8 sends the content image via -i/--icon (the image-path hint) while -n/--app-icon is app_icon. The spec adds the image contract field, daemon hint handling and theme-name resolution, the balloon preview, and a local-only inline image policy.
2026-09-15feat(desktop): make the drawer notifications readableDanilo M.4-16/+49
Each row shows when the notification appeared ("Sep 15 14:32", from created), sits on its own rounded card with space between rows instead of reading as one list, and uses the balloon's 16/18/16 sizing rather than 13/11. The close target grows to a 32px hit area in the row and the balloon, since a 20px box was hard to hit.
2026-09-15chore: ignore the superpowers SDD workspaceDanilo M.1-0/+1
The per-plan ledger, briefs and reports under .superpowers/ are scratch, not source. Committing the ignore rule keeps the tree clean for every clone and worktree instead of only this one, where it lived in the local info/exclude.
2026-09-15fix(notifications): apply final review fixesDanilo M.5-4/+26
Mirror the balloon's right-click close-all on the drawer row: the spec says the gestures are identical in both forms, and a right-click on a row previously did nothing. The right-button branch precedes the live guard so it behaves the same on history rows. Stop importing the Status singleton into the notification shell. Referencing it instantiated it, and its onPresentationChanged writes status.dnd and runs breaktimer.sh, so a read-only consumer was writing state and shelling out on every presentation toggle, and doubled the dndBeforePresentation race. Notify now reads "/run/user/<uid>/status.dnd" directly through a FileView, the same convention as the notifyd files; a missing file means off. AGENTS.md named Drawer.qml as the drawer's reserved space; it is desktop/NotificationList.qml.
2026-09-15docs(notifications): document the renderers and record the trapsDanilo M.2-0/+23
The daemon is a separate process and the files are the interface; suppression lives in the balloon shell so the drawer can list what DND held back; and the drawer flag is what stops a notification appearing as both a balloon and a row.
2026-09-15feat(status): add the snooze rowDanilo M.2-0/+124
A switch and a free-text minutes field driving notify-snooze.sh. Snooze is a file the balloon shell reads, so the row only writes it; the switch follows the file, including a snooze that ends while the page is open.
2026-09-15fix(desktop): leave history when deep-linking to a pageDanilo M.1-0/+3
show() is the IPC deep-link path and set only page/open, so if the history view was open, historyLoader stayed active alongside pageLoader and painted over the requested module page. Clear history in show(), as close() already does.
2026-09-15feat(desktop): add the notification history pageDanilo M.2-4/+92
Reachable only from the reserved space's History button, so it is a drawer view, not a module and not a tile. Rows are inert; clear-all empties the ring, since the daemon has no per-id history deletion.
2026-09-15fix(desktop): publish the drawer flag at startupDanilo M.1-0/+5
onOpenChanged does not fire for the initial value, so a restart while the drawer was open left notifyd/drawer at "1" and every balloon stayed suppressed until a drawer open/close cycle. Write the starting state from Component.onCompleted so the file always reflects the drawer.
2026-09-15feat(desktop): fill the drawer's notification spaceDanilo M.3-6/+199
The reserved Item becomes the live queue with a History button, scrollable because the queue can hold 20. The drawer writes notifyd/drawer so the balloon shell stands down while this space is showing, which is what stops a notification appearing twice.
2026-09-15feat(notifications): add the rofi action pickerDanilo M.1-0/+40
The renderer passes the action labels and keys as arguments, so the picker parses no JSON; a chosen label maps to its key and runs notifyctl action.