| Age | Commit message (Collapse) | Author | Files | Lines |
|
|
|
|
|
|
|
|
|
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Per-core load, the network rate, VRAM and the fan used to run only while
a PerfPanel existed, and closing the page cleared them. Opening the
drawer therefore always showed a fresh first sample and an empty network
graph, which made the page not very useful as a glance at what just
happened. Everything now samples for the whole session, so the graph
opens on the last minute of history.
The cost is the VRAM fdinfo scan, ~14ms every 2s, now unconditional; a
ponytail note beside it says to gate it on the panel again if that shows
up. The LAN address and the public IP check run once at startup instead
of on each page open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The Mail, Assistant and Ask presets pages got a Save and Discard pair in
the page header; the rest still had their own buttons wherever their
page put them: User's Save at the bottom, Wallpaper's Apply and Reset
beside the mock screens, Icons' Apply and Reset under the cursor cards,
HyprSunset's and HyprIdle's Save + Restart in the page. They now all sit
in the header SaveBar, which takes the page's own verbs (Apply and
Reset, Save + Restart) and a busy state.
HyprSunset and HyprIdle had no notion of an unsaved change, so their
button was always live. They now count as changed when what save()
would write differs from what was read, which enables Save only then,
gives Discard (a reload) something to drop, and marks them in the
sidebar; a window open no longer reloads them over unsaved edits. The
staged cursor moved from IconsTab into the Icons singleton so the
header and the sidebar can see it.
Git and autostart act a row at a time, and a theme applies on click, so
those have no bar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The qtmaildir, Assistant and Ask presets pages wrote a field on Enter,
and a switch or chip the moment it was clicked, so a half-finished
change reached the file and a slip could not be taken back. They now
edit a draft as fields are typed, and a Save and Discard pair in the
page header (SaveBar, through Page.actions) writes or drops it. Save
waits while a field holds a value that cannot be written, and a draft
over a file that changed on disk meanwhile is refused at Save rather
than written over it. A dot in the sidebar marks a page with unsaved
edits; drafts survive moving between pages. The four Mail pages share
one draft, since they are one file. Tool switches are staged too and
renamed on Save; model Load, Prefer and Restart stay immediate, being
actions rather than settings.
Live editing needed two rules. A field is no longer bound to its value,
which would reset the cursor and eat a typed trailing space; it follows
the value only when the draft moves under it. And a Repeater's model is
now the set of row keys (or a count), assigned only when that set
changes: an array rebuilt from the draft on each keystroke recreates
every row, including the one being typed in, which loses its focus.
Git, autostart's rows, HyprSunset, HyprIdle and User keep their own
buttons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
QtQuick.Dialogs' ColorDialog opened light on the dark desktop. It paints
from the Qt Quick palette, which qt6ct leaves at Qt's default here: its
custom_palette is off and the dark comes from the kvantum-dark widget
style, which Qt Quick does not use. Qt.labs.platform's ColorDialog falls
back to the widget QColorDialog in a QApplication, which this process
already is for the tray menus, and widgets take the kvantum style like
every other application.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
An account's chip colour and each tag colour were #rrggbb typed by
hand. A swatch beside each now shows the colour and opens Qt's own
ColorDialog, which writes the choice back as #rrggbb; typing still
works. The add-tag row has one too, filling its field. Setting.qml
gains a colour flag rather than a second component, since a colour
key is an ordinary text key with a swatch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
qtmaildir is configured through qtmaildir.conf alone, which it reads at
startup and never writes. Four settings pages under a new Mail group
edit it: Mail ([general], [sync], [completion]), Compose, Accounts (a
card per account, add and remove) and Tags & shortcuts ([tagcolors] and
[keys]). Fields save on Enter and unset when emptied, with qtmaildir's
defaults as placeholders and numbers range-checked; the pages say when
qtmaildir is running and a change waits for its restart.
Ini.js edits the file line by line so its comments survive. Headers are
only whole [name] lines: a section is never cut at the next "[", since
values such as [Gmail]/Bozze hold brackets, the trap AGENTS.md records.
The shortcut actions are copied from qtmaildir's KeyMap::knownActions()
rather than exported by it, since a CLI flag there would have meant
translations and a changelog entry; the field also takes free text, and
the copy is marked as a ponytail ceiling.
The settings sidebar now clips, which a list long enough to scroll
showed it did not: entries drew over the search box.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Load set the preference as well as swapping the model in, so loading a
model from the settings page for any reason made it the assistant's
model too: after loading Qwen there, the canvas kept Qwen instead of
bringing the preferred Gemma back. Load and Prefer are now separate
buttons: Load swaps a model in now and leaves the preference alone, and
Prefer only records which model the canvas loads when it opens.
The canvas header also showed a stale model name after a swap; it now
takes the model from the daemon's ctx event, which carries it from
desktop-assistant f9d4644 on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A preferred model that only took effect from the settings page did
nothing the next time another client had swapped it out. On this
desktop one model and one client run at a time, so opening the canvas
is when the assistant's model should come back: if the preferred one is
neither loaded nor loading, it is loaded, and the canvas header says so
until the router reports it in. The daemon still never asks for a model;
with no preference set nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The daemon answers with whichever model llama-server has loaded and
never asks for one, so it never evicts a model another client is using.
The Assistant settings page now lists the router's models with their
state. Load saves one as the preferred model, in Prefs, and asks the
router to load it now, polling /v1/models until it reports loaded;
Prefer marks the loaded model as preferred without swapping. The swap
happens only on that click, so the daemon's rule holds.
AGENTS.md records the router's load endpoint and its two refusals, both
measured: it lives on the server root, not under /v1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Ten pages in one flat list had stopped reading as a list. A module now
names its group, and the sidebar sorts by it and draws a small heading
where it changes: Account (user, git), Appearance (wallpaper, theme,
icons), Desktop (HyprSunset, HyprIdle, autostart) and Assistant (the
daemon, Ask presets). Groups keep the order their first page is
declared in, so shell.qml's module order still decides.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Two settings pages for desktop-assistant.
Assistant edits the daemon's config.toml: server, temperature, tool
rounds, thinking, the system prompt, and the [env] values its tools
read; a switch per tool renames tools/<name>.toml to .toml.off, which
the daemon's glob skips. The daemon reads all of it at startup only, so
the page says when a change is pending and Restart runs assistant.sh
restart, showing its status when the daemon does not come back.
The file is edited line by line, like autostart.lua, so the comments
the example ships with survive: unset keys stay commented out in place.
Every write parses the whole new file with tomllib first and replaces
the old one atomically, so no edit can leave a config the daemon will
not start with. Defaults are read from assistant.py --defaults rather
than copied into QML.
Ask presets edits the canvas's canned questions, which Prefs already
stored for exactly this page: label, question, and the kinds each is
offered for. They apply live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Moving the appearance files into the desktop added import "../.." after
the last line of each file that began with "import". In Icons.qml that
line is inside the embedded Python cursor script, so the import landed
there: python3 died on a SyntaxError, printed nothing, and every cursor
card stayed empty with nothing in the log. Icons.qml uses no Theme, so
the line goes. Every other moved file has it above its root object.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The mock screens took a third of the width beside the list, which left
both cramped in the settings window. They now sit on top, sized to half
the width and capped so the upright screen stays under 200px, with the
notes and the actions beside them; the folder list takes the full width
and the rest of the height below. The actions are a Flow, so they wrap
instead of clipping in a narrow window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The drawer's greeting row stays, and clicking it now closes the drawer
and opens the user page in the settings window: Drawer.show already hands
a settings module to the window, so the row's handler is unchanged.
"More git settings" switches the window to the git page through the
same go() it used in the drawer. The page is searchable by the fields it
edits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The grid is for quick settings, and the window it opened is the
opposite, so a tile for it sat wrong. The gear now sits at the right of
the user header, a round button that closes the drawer and opens the
settings window on its landing page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A window-wide Shortcut rather than a Keys handler, so it works with the
focus in a field, where Escape is taken first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The labels and page titles only; the module names, and so the IPC verbs
and search keys, stay sunset and idle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The appearance shell existed because a wallpaper picker needed more room
than the 600px drawer, so it ran as its own process with a full-screen
overlay. The settings window now gives it that room, and its five tabs
become five settings pages in the desktop process: Wallpaper, Theme,
Icons & cursor, Sunset and Idle. appearance/ is gone, and with it a
process, a keepalive window and a layer blur rule.
The files move to desktop/modules/appearance/ unchanged in what they do.
Each tab gets a small *Module.qml; the window's refresh-on-open calls the
singletons' refresh(), which the panel's show() used to. The two tabs that
were inline Components in AppearancePanel become ThemeTab and
WallpaperTab, owning the notices the panel printed. Sunset, Idle and
Icons drop their own Flickable so the page scrolls them and search can
reveal a row; the wallpaper list virtualises its rows and scrolls itself,
so it takes the page's new viewHeight instead.
Search finds a colour scheme and an icon or cursor theme by name.
The appearance IPC target moves to desktop/shell.qml with the same verbs,
so udt-steps and any keybind change only the path. Git now follows the
appearance pages and autostart in the sidebar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The About text sat in the top-left corner of an otherwise empty page; it
is now centred both ways in the space beside the sidebar.
The sidebar's edge, the rule between it and the page, drags to resize it
between 160 and 420px, lit in the accent while hovered. The width lives
in Prefs, written once on release rather than on every pixel of the
drag, so it survives a restart without a file to edit by hand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A small x at the right of the field while something is typed. Clearing
puts the focus back in the field, ready for the next search. Escape
still clears from the keyboard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Search used to stop at the page: clicking a result opened it at the top
and left the setting to be found by eye. Now each settings module keeps
its rows as searchItems, {text, key}, reread on every window open since
the page itself is not loaded until shown: git runs gitcfg list, and
autostart watches its file. A row hit opens the page, and Page.reveal()
finds the row whose objectName is the key, waiting up to 3s for the
page's own data, scrolls to it and flashes it in the accent. The reveal
lives in Page, so a page only names its rows.
Git's long keyword list was a stand-in for exactly this and is cut to
"gitconfig". The autostart page takes its path from the module now that
both read the file.
Also corrects the AGENTS note on missed hot reloads: it blamed sed -i
replacing the file, but the Edit tool writes a new inode too and usually
reloads. The cause is unknown; the note now says how to detect it and
that a process that stops watching needs a restart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The landing page's tiles repeated the sidebar. It now says what the shell
is and where things live, which is what a landing page is for.
The sidebar gains a search. A page is a Component loaded only when
opened, so its contents cannot be indexed without loading every page;
instead a module lists keywords for the settings buried in its page,
and the search matches those and the label. An entry matched by a
keyword shows which one, Enter opens the first match, Escape clears and
then steps out as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The page header's back arrow closed the whole window, which reads wrong
for a back button. The window now opens on a landing page, a tile per
settings page in the drawer's own Tile, and back and Escape step out to
it; Escape there closes the window, as on the drawer's grid. An empty or
unknown name opens it, so the gear tile and ipc toggle land there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The global git config is set once and left alone, which is what the
settings window is for. The user row and its page stay in the drawer;
"More git settings" there now closes the drawer and opens git in the
window: Drawer.show hands any settings module to the window, so every
deep link, IPC included, lands in the right place.
Git was the only user of Module.up, the page back() returned to, so
up goes with it and back from a drawer page is always the grid.
Also gives autostart its rocket glyph, which the first commit wrote as
an empty string.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The drawer's tiles were turning into a full settings app. The drawer stays
the quick area, live state and things toggled often; pages set once and
left alone go to a settings window, a normal Hyprland toplevel listing
them down the left. A module joins with settings: true, which also drops
its tile. A gear tile and ipc call settings open|toggle reach it.
Autostart edits hypr-theme's autostart.lua directly, line by line: an
hl.exec_cmd line is an entry, behind -- it is disabled, everything else
passes through. Edits refuse when their line moved since the parse, and
writes are atomic because Hyprland reloads on every change.
A wrong entry only shows at the next login, so adding is guided rather
than a free text field: pick an installed app or type a command whose
program must resolve on PATH before Add enables, then a workspace and
silent, composed into a [workspace X silent] rule. Try runs the line
through hyprctl dispatch so the rule applies as at login. Existing rows
get no Try: they ran at login, and a second start stacks an instance.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
|
|
Reported from use: a note created with only a title then saved left a db.json
entry with no file beside it, and reopening it said the file was missing.
Cause: FileView.setText("") on a path that view has not loaded creates no
file at all and reports no error. The index write (always non-empty JSON)
was fine, so only the body was lost, which is why it looked like a missing
file rather than a failed save. Bodies are now written by a Process running
cat > <file> with the text on stdin: verified against empty and non-empty
payloads, consecutive writes on one Process, and a store with no db.json at
all. The exit code is the real outcome, so a refused write reports it.
Second report in the same flow: the editor stays editable after the first
click, so typing more and then pressing Commit & push dropped the edit, the
push carrying only what the first click wrote. The second click now writes
the current title and body before it commits and pushes.
|
|
The confirm was on the second click, after the first had already removed the
file and the entry, so the 'Confirm?' label promised a check that had
already happened and one stray click lost the note. The first click now only
arms the button; the second removes and pushes the removal.
|
|
A review of the branch found four ways the page could report success for
work that never happened, all reachable in ordinary use.
Writes: setText is asynchronous unless blockWrites, and its
saved()/saveFailed() do not fire for every write (a second setText to the
same path loses the signal, which stranded the callback), so a save was
reported before it landed and a failed db.json or body write was never
seen. Both views now block and carry the outcome, so writeDb and writeBody
know the result when they return; a failed create therefore leaves no index
entry. The create path also loads the body before the index, so a failed
body write cannot leave an entry with no text.
Commits: the sync chain swallowed every commit failure with '|| true', so a
failing pre-commit hook or a signing failure pushed an unchanged HEAD and
reported success. It now stages, commits only when something is staged, and
lets 'set -e' fail the chain, with stderr folded in for the whole chain
rather than just the push.
Duplicate notes: the 'awaiting commit' state lived only in a 3 second
timer, so a slower second click re-entered create() and appended a second
note. It is now a page property that outlives the timer, and the button
follows it.
Preview: the shared Switch writes checked itself, dropping the binding, so
the switch stopped following page.preview; resynced on change, the way
StatusRow.qml does.
Also validates a note's file name as digits before read, write or rm, and
clears the index error once the index reads again.
|
|
|
|
The editor, and the write and git path behind it. Save is two clicks and
each does one thing: the first writes the note and only then arms the
button as 'Commit & push?', so a quick second click cannot commit a note
the first has not written yet.
The write path refuses when db.json cannot be read as the index. That is
not hypothetical: git pull --rebase --autostash resolves a remote db.json
change against a local uncommitted one, the stash pop conflicts, git still
exits 0, and the file is left full of merge markers. Reading that as 'no
notes' and writing it back deleted every title in a probe; Notes.valid now
distinguishes it from an empty store and the write reports instead. parse
stays tolerant for display. Stamping lastpull also only follows a pull
that worked, and only when git is on at all.
Db reads that a write is based on go through reload() plus waitForJob():
blockLoading does not make reload() blocking, contrary to the note in
AGENTS.md, and reading text() straight after reload() returns the old
contents. A body is written with a plain setText, which needs no prior
load; only reading text() before a load would.
|
|
The module resolves notes.sh's own paths by sourcing its rc, so a changed
BASEDIR moves the page with it, and notes.sh's git test is the same one the
page uses. A Module is a plain QtObject with no default property, so the
config Process and the db.json FileView are typed properties, the shape
UpdatesModule uses for its own Process.
db.json is watched rather than read on open: the tile's count has to move
when a note is added from either side, the CLI included, which is the same
call mail makes for its unread count.
The list reads db.json.notes rather than walking notes/, because that is
where the titles are; entries sort newest first on the file timestamp and
a title search filters them.
|
|
The parsing, sorting and transforms the notes page needs, kept free of any
file or process so node can test them.
Ids and files stay strings: notes.sh writes them with jq --arg and looks a
note up with select(.id == $i), so a number would stop the CLI finding a
note the page wrote. sorted() copies rather than reorders, and an entry
whose file is missing or non-numeric sorts last and formats as blank
instead of NaN.
|
|
The Do tile's hover and press fills were Qt.alpha colours, and the
Glow sits beneath the tile (z: -1), so the translucent fill let it
wash out the icon and the label. Qt.tint over Theme.surface keeps the
fill opaque, so the glow reads as a glow around a solid tile rather
than through it.
|
|
The canvas: a To .md ended any other way than its own Stop (the footer
Stop, a question, a drop, a daemon restart, an older daemon refusing the
command) left the card at Working forever. The footer Stop goes through
stopOp(), asking and dropping end the operation first, a disconnect ends
it, and an error while nothing else runs is taken as its outcome. Each
fileops run gets its own Process, so a stopped run's late answer is never
read as the next one's. To .md takes one PDF, as one file is written.
Compress... no longer flips back when file(1) refines a kind, long errors
are elided, and a desktop with no documents folder gets
Documents/Assistant rather than ~/Assistant.
fileops: an animated GIF to PNG or JPEG left one file per frame under
names the rename never found; the first frame is taken. JPEG flattens
transparency onto white. Zipping a folder holding the output folder read
its own growing archive without end; only regular files outside it are
added, which also skips FIFOs and dangling links. qpdf warnings no longer
fail a merge. A folder's free name keeps its dots, a lone .gz or .xz is
decompressed rather than refused, and a dotfile's archive is not hidden.
Tools die with fileops even when it is killed outright, a killed run's
leftovers are swept on the next run, a stop between fork and bookkeeping
cannot miss the child, and a late stop cannot add a second answer.
Error lines lose bsdtar's doubled prefix and its delayed-exit noise.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The actions sat as buttons bunched at the left of the card. Each group is
now a grid of equal tiles across the card's width, icon over label,
about 150 px a column, with a small Ask or Do caption. Icons are Font
Awesome and Octicons codepoints checked in Inconsolata Nerd Font's cmap;
a saved preset without one gets a fallback. The lists referenced from
QML are var, since a library's top-level const is not reliably exposed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Conversions, extraction, merge and compression on the staged files from
the file state, one at a time, with Stop; Compress… opens its formats.
To .md goes through the daemon's write, the rest through fileops. The
card keeps the result line, Saved name with Open and Folder, or the
error, so another operation can follow on the same files. Results land in
Documents/Assistant.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
One CLI for converting, extracting, merging and compressing staged files,
answering with one JSON line. Results are written under a temporary name
and renamed to the first free one, so a failure or a stop leaves nothing
half written, and a stop kills the tool's whole process group. This
ffmpeg has no libx264 and no AAC encoder, so MP4 is H.264 through VAAPI
with MP3 audio, and audio extraction copies AAC, MP3 and Opus tracks as
they are. The test builds its inputs and checks every result's type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A script with no suffix staged as 'other' and was refused. A staged file
the name says nothing about is now handed to file(1) once, and its mime
type gives the kind, so a shell script is code and gets Explain and
Review. The daemon makes its own call on the content.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A row of canned questions matching every staged file's kind, plus Compare
for two or more files: one click asks it, Shift+click puts it in the input
to edit first. Defaults live in Presets.js and the list in use in Prefs, so
a future drawer settings page can edit it; a saved list that does not
validate reads as the defaults. Each prompt asks for an answer in the
document's language. Code and configuration get their own kind, so a
script is offered Explain and Review rather than Summarize. Button's
clicked signal carries the mouse event for the Shift check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A drop offering no local file:// URL was refused silently: the bubble
vanished and nothing happened. Measured on the first report: the file
manager offers a trashed file as trash:, with no path to read. The bubble
now shows the reason for a moment, Trash named as such, and the refusal is
logged with the URL schemes and offered formats (never the paths).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
From review. A shell reload reconnected with lastActive at 0, so the next
SUPER+Home started a new conversation thirty seconds into one; hello with
turns now counts as activity. show() could send new under a reply still
running, which stops it unsaved; it waits while busy. ask() cleared the
chips before the daemon accepted them, so a refused file (unreadable, too
big, a model without vision) dropped them and collapsed the card to the
entry line; the user event clears them now, and chatting holds the layout
while a PDF is read.
In the picker, folders ignore name filters and sorted first, so filter
then Enter opened a folder; folders hide while filtering. Folder URLs are
encoded, so a '#' in a name no longer cuts the path, and glob characters
typed in the filter are taken literally. The card says the assistant is
not running in every state, panels sit under the chat header instead of
over it, and History and the picker no longer open together. Dropped
file://localhost/ URLs are accepted, other hosts refused, and the island
receptor keeps the sliver's width at rest so it takes no clicks beside it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A DropArea over the sliver grows into a bubble while a file drag is over it and hands file:// paths to the canvas.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
|