aboutsummaryrefslogtreecommitdiffstats
path: root/desktop
AgeCommit message (Collapse)AuthorFilesLines
6 daysqtmaildir settings: the colour dialog follows the desktop's dark styleDanilo M.1-6/+13
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>
6 daysqtmaildir settings: a colour picker for colour fieldsDanilo M.5-12/+86
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>
6 dayssettings: qtmaildir's config, in four Mail pagesDanilo M.20-0/+1235
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>
6 daysassistant: Load no longer changes the preferred modelDanilo M.4-15/+27
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>
6 daysassistant: opening the canvas loads the preferred modelDanilo M.5-2/+33
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>
6 daysassistant: pick the preferred model, loaded on demandDanilo M.4-1/+145
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>
6 dayssettings: the sidebar is groupedDanilo M.13-33/+74
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>
6 dayssettings: the assistant's daemon config, tools, and Ask presetsDanilo M.11-2/+1115
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>
6 daysicons: cursor previews load againDanilo M.1-1/+0
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>
6 dayswallpaper: the preview above the listDanilo M.2-111/+108
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>
6 daysuser: the details page moves to the settings windowDanilo M.2-5/+9
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>
6 daysdrawer: the settings gear moves from the grid to the user headerDanilo M.3-14/+33
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>
6 daysdocs: Ctrl+Q in the settings windowDanilo M.1-1/+1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 dayssettings: Ctrl+Q closes the windowDanilo M.1-0/+7
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>
6 dayssettings: HyprSunset and HyprIdle, named after what they configureDanilo M.3-6/+6
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>
6 daysappearance moves into the settings windowDanilo M.26-29/+3178
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>
6 daysdesktop: centred About, and a resizable settings sidebarDanilo M.3-35/+88
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>
6 daysdesktop: a clear button in the settings searchDanilo M.1-1/+19
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>
6 daysdesktop: settings search finds rows, and a hit reveals its rowDanilo M.8-24/+149
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>
6 daysdesktop: settings search, and an About landing pageDanilo M.5-38/+134
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>
6 daysdesktop: the settings window gets a landing pageDanilo M.2-8/+51
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>
6 daysdesktop: git config moves to the settings windowDanilo M.8-16/+23
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>
6 daysdesktop: a settings window, and autostart as its first pageDanilo M.9-1/+900
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>
7 daysnarrow the sound module's real player listDanilo M.1-1/+9
7 daysnotes: an empty note body gets a file, and the second click writesDanilo M.3-31/+54
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.
7 daysnotes: delete asks before it destroysDanilo M.2-16/+25
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.
7 daysnotes: write and commit failures are reported, not assumedDanilo M.5-73/+153
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.
7 daysnotes: component READMEDanilo M.1-0/+53
7 daysnotes: in-drawer editor with commit-on-save and git pullDanilo M.4-75/+555
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.
7 daysnotes: drawer tile with count and the note listDanilo M.3-0/+245
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.
7 daysnotes: pure db.json helpers, node-testedDanilo M.2-0/+183
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.
7 daysfix(assistant): lit tile opaque over the glowDanilo M.1-2/+4
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.
7 daysfix(assistant): review of the Do row and fileopsDanilo M.6-40/+178
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>
7 daysfeat(assistant): Ask and Do as captioned tile grids with iconsDanilo M.3-66/+135
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>
7 daysfeat(assistant): the Do row of file operationsDanilo M.5-6/+273
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>
7 daysfeat(assistant): fileops, the Do row's local operationsDanilo M.2-0/+366
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>
7 daysfeat(assistant): tell a nameless file's kind from its contentDanilo M.4-7/+67
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>
7 daysfeat(assistant): Ask presets in the file stateDanilo M.8-13/+171
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>
7 daysfix(island): say why a drop is refusedDanilo M.2-6/+24
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>
7 daysfix(assistant): keep state across reloads, refusals and long repliesDanilo M.6-14/+41
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>
7 daysfeat(island): take a file drop for the assistantDanilo M.3-0/+64
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>
7 daysfeat(assistant): entry line, file state, staged files and a pickerDanilo M.6-29/+415
The card has three states: an entry line, a file state for staged files with Chat about it, and the chat. SUPER+Home resumes a conversation active in the last 10 minutes and otherwise starts a new one. The paperclip opens a picker drawn in the card, since a system dialog would open beneath the overlay that holds the keyboard. Button gains a font alias for the paperclip glyph, and the canvas a socketPath property so a check can point it at a scratch daemon. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
7 daysfeat(assistant): file kinds and dropped URLsDanilo M.2-0/+95
Files.js decides what the card offers for a staged file; the daemon decides what chat accepts, so the text and image lists match its own. Dropped URLs become paths only for file:// ones. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
7 dayschore(assistant): retire the voice overlayDanilo M.2-3/+3
desktop-assistant is a text daemon now (its voice version is the tag voice-v1), and the canvas in desktop/assistant/ is its front end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 daysfix(assistant): keep focus in the card, scope errors, guard linksDanilo M.7-15/+55
From review of the canvas. Escape stopped working in three ways, each leaving the keyboard grabbed with nothing to receive it: the history field hiding took focus with it, a disabled input could not hold focus while the daemon was down, and a click in a streaming reply focused text the next flush destroyed. The card now gives focus back to the input whenever it falls out, and the input is read-only rather than disabled. Only an error the daemon marks as ending the reply (turn) closes the live row; a command error, possibly another client's, used to freeze the reply mid-stream. The running tool comes from state events, hello included. Scrolling up during a stream no longer snaps back, since following stops when a movement starts. History snippets go through Notify.sanitize like any model output, links open only for http, https and mailto, and a line opening with inline triple-backtick code is no longer taken as a fence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 daysfeat(desktop): host the assistant canvasDanilo M.1-0/+11
Inside the desktop process rather than its own shell: it needs no keepalive window of its own and restarts with the rest of the desktop. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
7 daysfeat(assistant): the canvas, a view over the assistant daemonDanilo M.6-0/+783
The socket, the turn model and the window live in Assistant.qml, outside the window's loader, so closing the card loses nothing and a reply keeps streaming while it is closed. Tokens reach the model on a 50 ms tick rather than per token. Prose goes through Notify.sanitize, because model output can quote a search result and a remote image in it would be fetched on render. Code blocks get a copy button once their fence closes. The history panel lists conversations and searches them through the daemon's full-text index. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
7 daysfeat(assistant): split messages into prose and fenced codeDanilo M.2-0/+104
Qt's markdown renders a fence but gives no per-block hook, so a copy button per code block needs the fences found first. An unclosed fence is open code, which is every fence while a reply streams; a fence inside a list item keeps its indent out of the copied text. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
7 daysfeat(island): a media pill left of the clockDanilo M.3-2/+183
Shows album art and a scrolling "artist · album · title" while a player is playing or paused, using the sound module's Player so the browser proxy duplicates are already filtered. Hovering swaps the text for a progress bar, swapped in place rather than growing the pill; MPRIS does not push position, so it is re-read every second while hovered, and a click seeks only when the player reports canSeek and a known length. The wheel steps tracks one per notch, as the layout flag does, so a touchpad does not skip several. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 daysfeat(git): add keys per section with a + beside each headingDanilo M.2-102/+131
The single Add line at the bottom took a full section.key and left the user to type the section right. Each heading now carries a + that opens a key and value row under it; git config --add writes the key into that existing section, among its neighbours, rather than at the end of the file. The typed text lives on the page, not in the fields, so a failed add keeps it across the reload that rebuilds every row. A new section is made in the editor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>