| Age | Commit message (Collapse) | Author | Files | Lines |
|
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.
|
|
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.
|
|
Rows for each listener with an enable toggle and a timeout field, the
command shown read-only. Save rewrites the file and restarts hypridle.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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>
|
|
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>
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
Deriving the window's visible from column.implicitHeight deadlocked it:
a hidden window stops polishing, Column computes implicitHeight during
polish, so once the column was empty the window never re-mapped and no
later balloon could show, including at startup where the queue arrives
asynchronously after the window is already hidden. The binding now reads
Notify.queue membership, which a property binding re-evaluates whether or
not the window is mapped, so a later notification re-maps it. Expired
entries left in the queue keep a zero-height window mapped, which is
harmless.
|
|
The Repeater model was a fresh filter of Notify.queue on every 250ms tick
because the filter read now, so Qt Quick recreated every balloon delegate
four times a second, reloading icons and resetting hover. Model is now
Notify.queue itself, and each balloon drops itself at expiry through its
own visible binding, driven by the shared tick.
Removing required from the balloon's properties is part of the same fix: a
required property makes QML create the delegate in its own context, where
modelData and index are undefined, so notification: modelData silently
arrived undefined once a live queue item was actually drawn.
|
|
Reads the daemon's queue through the Notify singleton and draws a balloon
per live notification, bottom-right of DP-1 over conky. Suppression is here,
not in the daemon: dnd withholds low and normal, snooze withholds all, and
the drawer still lists them.
|
|
The daemon publishes its queue, history, drawer flag and snooze as files;
this reads them for both renderers, the same files-are-the-interface
convention the status registry set. Mutations and the rofi action picker run
notifyctl through a Process, which is the one path back to the daemon.
|
|
Plan two of two: the quickshell side of the notification daemon. The Notify
singleton reads the daemon's published files, a new notifications/ component
draws balloons and owns suppression, the drawer's reserved Item becomes the
live queue with a history page, and the Status page gains a snooze row.
Suppression lives in the balloon shell so the drawer can list what DND held
back, and the drawer flag is the single no-double-show mechanism. Per-id
history removal is a tracked TODO in the notifyd repo rather than an
untracked spec gap.
|
|
The freedesktop spec says -1 means the server decides and 0 means never; the
spec and plan had them reversed, which the shipped code inherited and the
live handover test caught. Corrected to match the spec, libnotify's -1
default and dunst.
|
|
The check has five assertions, not four.
|
|
The daemon binary was built at $tmp/notifyd, the same name RuntimeDir() gives
under XDG_RUNTIME_DIR, so the two collided. The runtime dir moves to $tmp/run,
which is what the shipped script does.
|
|
The test snippets omitted the header the global constraints require on every
.go file, which a task review flagged against the first one shipped. The
snippets now carry it, so later tasks do not repeat the gap.
|
|
Public under the Linux cgit section, cloned to ~/Programming/GIT/notifyd, so
Task 1 Step 1 is done and the executor starts at Step 2.
|
|
Plan one of two for the notification daemon: the Go daemon and notifyctl in
their own repo. The renderer plan is separate because the daemon is working,
testable software on its own, exercised through notifyctl.
The policy is pure and tested without a bus; the store's expiry, dismissal
and eviction are separate lifetimes, which the store tests pin; and
test-notifyctl.sh runs the daemon and CLI on a private session bus against a
temporary runtime directory.
|
|
The freedesktop expire_timeout is the notification's lifetime, and --wait
and -b clients block until the daemon closes it. So the daemon emits
NotificationClosed at expiry and frees the client, but keeps the entry in
the live queue: the drawer lists it until it is dismissed or evicted, inert
because its client is gone. History holds only what was dismissed or
evicted.
The reserved space is therefore scrollable, since the live queue can hold
20 at once, and a row past its expiry is read-only.
|
|
The second of the two specs the status registry named. A Go daemon owns
org.freedesktop.Notifications and holds the state; quickshell renders it.
The daemon and notifyctl live in a separate repo, the balloon shell and the
drawer's notification centre live here.
Rendering is split because quickshell has no generic D-Bus module, so
something outside QML has to own the bus name. Files are the interface in
the direction the renderers read, the same convention the registry set, and
notifyctl is the one surface back.
DND suppression lives in the balloon shell, not the daemon: the drawer's
reserved space lists every live notification including the suppressed ones,
because a list the user opened is not an interruption. This supersedes the
registry spec's assumption that the daemon would read dnd.
|
|
F1: after a click the shared Switch writes checked directly, dropping
StatusRow's declarative binding, so an external mode change from waybar
or statusctl no longer moved the switch. Resync via onValueChanged.
F2: the spec claimed hyprctl clients counts idle inhibitors, which is
false for a layer-surface inhibitor; replace with the behavioral
hypridle marker check.
F3: README said the check covers both watch states; it exercises the
activated report and the absent-file down report.
F4: give breakProc an onExited check so a failed breaktimer.sh verb is
visible instead of silent.
F5: drop StatusTile's unused required st property and its injection.
F6: comment the startup-order limit on dndBeforePresentation.
|
|
A FileView fires its own fileChanged on setText, so a handler that writes
in response to a change loops unless it compares first. IdleInhibitor has
no window of its own and does nothing without one, so the singleton is
handed the keepalive window by shell.qml.
Both were read from the documentation while designing and confirmed while
implementing.
|
|
read_mode() failed on a missing mode file with a shell redirection
error, 'statusctl: line 56: .../status.dnd: No such file or directory',
printed to stderr on every get/toggle before the file existed and
polluting waybar's exec stderr.
The '2>/dev/null' sat on the 'tr' command, but the error is the parent
shell's input redirection failure, which that redirect cannot suppress.
Guard on '[[ -e "$file" ]]' before reading; a missing file still
reads as 0, which was always the contract.
Mirror the fix into the plan so plan and script agree.
|
|
One row per mode. Adding a mode is one file in the singleton and one row
here, which is what a registry buys over two separate toggles.
|
|
The module is thin because the singleton owns the modes: it is a tile and
a page over Status, always active so the effects hold whether or not the
drawer has been opened.
Injected as st rather than mod, since a component property named the same
as the enclosing object's id binds to itself and arrives undefined.
|
|
Presentation mode sets DND, asserts a Wayland idle inhibitor and pauses
breaktimer. The effects hang off the mode property rather than the setter,
so a mode set with statusctl while the drawer is closed asserts them too.
DND has two writers once presentation mode exists, so turning presentation
off restores the value DND had before rather than clearing it, or an
afternoon of hand-set DND would vanish when a talk ends. That prior value
lives in the singleton, not in a file: it means nothing once presentation
mode is off, and presentation mode does not survive a reboot.
breaktimer owns its own state file and is driven only through its verbs.
Two writers on that file would race with its daemon loop.
|
|
IdleInhibitor needs a non-null window and this is the only one that lives
for the whole session, so presentation mode attaches to it. Naming it is a
prerequisite for that and changes nothing else.
|
|
Two corrections to the plan, both found by running it rather than reading
it.
The test script the plan specifies makes nine assertions; the prose around
it claimed eight, in both the task step and the final check. An implementer
running it would see a passing suite that disagrees with its own expected
output, which reads as a failure of either the test or the plan. My
arithmetic.
The plan's statusctl also swallowed a failed rename and read the mode file
through cat piped into tr, so a write that did not land still reported
success and an unreadable file fell back to off by accident of pipeline
semantics rather than by intent. Review of the implemented script caught
both. The plan now carries the same fixes, so a future run of it does not
reintroduce them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7ThHHh5iTYbVfp3rNAkw2
|