馃殌 Release BOSS 9.5.7
BOSS v9.5.7
Release Date: 2026-09-03
Five changes. The host gains the git and project-search plumbing that IDE-style plugins run on - per-file, commit and ref-range diffs, find-in-files with replace, and diff and composer tabs. Alongside it, the host's own action buttons stop floating over content the window had room for, a Favorites tile shows the site's own favicon instead of Google's guess about its host, a two-finger swipe decides at the end of the gesture instead of the instant it crosses the line, and one Cmd+W in a browser tab stops closing two tabs.
Highlights
- Git diff, project search and two new tab types, host-side.
GitService/DesktopGitServiceserve per-file, commit and ref-range diffs plus window-scoped fetch/pull/push and ref-scoped log;ContentSearchServicebacksProjectSearchProviderwith find-in-files and replace;DiffTabInfoandComposerTabInfoland inplugin-api-corewith workspace persistence. This is the half that editor-tab, codebase, git-status and git-log consume through boss-plugin-api 1.0.87 (#289) - The host's actions no longer park an always-on-top cluster in the content area. With the top bar off, Sign Out, Settings, Toolbox, Tools and Search fell back to a floating cluster with no click-through, taking that corner from whatever was underneath. A collapsed vertical tab bar now hosts them at the foot of its rail, and a top-bar window with a plugin panel open hosts them at the foot of that panel's column - measured, so a column too narrow to carry them falls back rather than starving the plugin (#319)
- A favourite shows its own favicon, not one derived from its host. The tile fetched from Google first and read the page's cached favicon only on failure, so
mail.google.com,docs.google.comandaccounts.google.comall resolved to one 32px Google "G" - byte-identical - and agoogle.com/urlredirect bookmark resolved to Google rather than the site it opens. The order is reversed, and Google's "no favicon here" answer (HTTP 200 with a 16x16 globe, identical bytes for every unknown host) is rejected by SHA-256 instead of cached as an answer (#318) - A two-finger swipe commits when the gesture ends, not when it crosses the line.
onWheelcallednavigate()inside the same event that crossed the commit distance, so reaching the distance and committing were the same instant and the chevron affordance had nothing to warn about. The decision moved to the end-of-gesture timer: holding past the line and releasing navigates, easing back below it cancels (#301)
Features
- git: per-file, commit and ref-range diffs, window-scoped remote verbs (fetch/pull/push) and ref-scoped log, behind
GitDataProviderImpl. Diff reads resolve the window's project path rather than the global one, so a second window cannot redirect them (#289) - git:
UnifiedDiffParserturns git unified output intoGitDiffData- renames, binary files, C-quoted non-ASCII paths with octal bytes decoded as UTF-8, omitted@@counts - and refuses combined merge diffs rather than mis-numbering them (#289) - search:
ContentSearchServiceimplements find-in-files and replace behindProjectSearchProvider. Exclusion runs inside the walk somaxResultsbounds wanted matches, and one sharedcomputeReplaceddrives both the disk and open-buffer paths so literal and regex expansion cannot diverge (#289) - tabs:
DiffTabInfo/DiffTabTypeandComposerTabInfo/ComposerTabType, with workspace persistence that drops a scope it cannot restore rather than rebuilding it as something else.DiffOpenEventonFileEventBusis window-filtered, so a diff opens in the tab that asked for it (#289) - panel: a collapsed vertical tab bar hosts the host actions at the foot of its rail.
verticalBarHasFootbecameverticalBarHost, returning NONE / FOOT / RAIL, so "a foot AND a rail" stops being a state the types admit (#319) - panel: with a top bar and a plugin panel open, the actions sit at the foot of the right column.
hostActionsPanelEdgeis also the answer to whether there is a panel foot at all, so the two cannot disagree (#319) - git: a
gitCommandsRunningStateFlow and a top-bar hint, so an index write holding the process-wide git lock is visible instead of a silent ten-minute timeout (#289)
Bug Fixes
- browser: one Cmd+W in a browser tab closed two tabs on macOS. The chord reaches both the JxBrowser native key callback and the AWT keymap, and only the zoom chords were exempted from the native layer - so the active tab closed and then its newly-active neighbour did. N/T, W, Shift+F and Shift+S are now gated on the same condition as zoom. Same mechanism fixes Cmd+T opening two tabs, Cmd+N opening a tab plus a window, Cmd+Shift+F double-toggling focus mode and Cmd+Shift+S saving the workspace twice (#290)
- git: the GIT tab could stick permanently on a stale "not a repository" verdict.
ensureRepoStateLockedre-probed only when the selected path changed, so agit initunder an open panel - or a first probe losing the startup race against the async git-availability check - was cached forever, and neither Refresh nor any later poll could recover (#289) - browser: a swipe could navigate twice. A slow drag that hesitates past the script's 120ms gesture gap with the fingers still down is two gestures to the page, and the guard cannot live there because the first commit navigates the tab and takes the script's state with it. The host holds two windows now: 32ms in any direction for a double-dispatch bug, 400ms same-direction for the paused drag. A reversal is never held (#301)
- browser: past the commit distance, vertical drift stops cancelling. Vertical is a path length and only grows, so every event after the crossing was one more chance to cancel a swipe the user had already completed - including a momentum tail's
deltaY, which clears the high vertical tier on its own (#301) - panel: the panel foot could starve the plugin it sits in. Measured, not reasoned about: a 20x400 column left the plugin 211dp behind 188dp of chrome whose icons were 4dp wide, and 20x200 left it 11dp.
panelFooterFitsColumnnow gates it - one icon at full size, at most two wrapped lines, at most a third of the column - and the placement falls back to the floating cluster when it does not fit (#319) - panel: the rail and the hover-reveal drawer are composed together, so handing one slot to both drew the full bar's wrapping row twice - four lines of squeezed icons, visible for the frame after dismissal. The rail has its own slot (#319)
- panel: the panel foot painted no background, and nothing is not the background - the raw native window surface showed through as white, with near-white icons on it (#319)
- panel: the foot belongs to the right column alone. Left was moving Sign Out across the window to dodge an overlap that does not happen (the cluster parks bottom-right), and disposing and re-creating the overlay's native window on every left-panel toggle; a bottom panel spans the window, and at its 20dp floor the plugin gets no height while the icons shrink to about 8dp (#319)
- browser:
https://example.com\@evil.com/extractedevil.comas the favicon host. WHATWG treats a backslash as a slash for special schemes, so Chromium opens that URL on example.com - but splitting the authority on/alone left the credential strip taking everything after the last@. The result was the wrong icon on the tile, a request to Google naming a site nobody visited, and that name cached for a fortnight, reachable by anyone who can put a URL in a bookmark file (#318) - browser: a host that dropped its favicon kept serving the removed icon.
fetchFromGoogledistinguished "no answer" from "no icon" for the miss memory and thenhostIconcollapsed both to null;FaviconFetchnow carries it, soNoAnswerserves the stale entry (offline costs sharpness, not the icon) andNoIcondrops it (#318) - browser: a cache entry dated in the future never expired. The age test was
now - fetchedAt > MAX_CACHE_AGE_MS, so a negative age - a file restored byrsync -afrom a clock-skewed machine, or a clock that stepped back - read as fresh forever, which is exactly the permanence the TTL exists to remove (#318) - browser: a failed delete cost the icon for six hours.
File.delete()returns false silently under a Windows lock, and the remembered-miss branch then returned null with a usable icon on disk. It falls back to the surviving entry, and the failure is logged on the postcondition rather than the return value (#318) - browser: the 32px width floor rejected every genuine 16px favicon, leaving a letter where a real icon existed (#318)
- browser: nothing asks Google about a URL it can have no answer for -
file://,boss://, a bare path, previously sent as a "domain" offile:- and the port, credentials and fragment are stripped, solocalhost:3000resolves aslocalhostrather than 404ing (#318) - browser: the capture picker decoded the same PNG twice per row, and derived its cache key with
as? FluckTabInfo, so a dynamic plugin tab's key - which only the reflection branch finds - never reached resolution at all (#318) - git:
unstageAll,discardChangesandcommitrefresh the repo that was written, not the global one; the write verbs and the terminal variants take aprojectPathOverridefrom the provider's window, closing the last wrong-worktree data-loss path (#289) - git: an untracked file diffs
/dev/nullagainst itself, showing all-added rows, instead of opening a blank tab that reads as "no changes" (#289) - git:
checkoutno longer strips a slash unconditionally - a slashed local branch checks out as-is, and theorigin/featuretofeatureDWIM applies only when no local branch exists (#289) - git: an apostrophe in a branch name no longer produced a broken command in the Windows terminal. A third, POSIX-only copy of the quoting is deleted and
mergeInTerminal/rebaseInTerminalroute throughCommandProcessor.quotePath(#289) - git: a stderr flood could mark a complete stdout truncated and refuse the diff; stderr has its own drop flag now. The stranded
index.lockhint is gated on index-writing subcommands, so a timed-outgit diffno longer advises deleting a lock another process legitimately holds (#289)
Improvements
- git: every plugin-reachable ref is validated with
isSafeRefNameand revision lists terminate with--, closing an arbitrary-file-write throughgit diff --output=that was reachable from read-only MCP tools (#289) - git:
runProcessBoundeddrains both pipes on daemon threads before a bounded wait, so a hung or chatty git command cannot deadlock or freeze the app-wide lock, and output is capped at ~32MB per sink - a whole-file-context diff of a large commit could otherwise put hundreds of MB on the heap while the git lock is held (#289) - git: the diff getters retry once at
-U3when the full-context stream blows the cap, and refuse a truncated stream with an in-band "too large to render" marker rather than rendering a partial diff that looks complete (#289) - git:
gitCommandLockis a coroutinesMutex, so a commit parked on gpg pinentry suspends its waiters instead of parking oneDispatchers.IOthread per queued caller (#289) - git: the diff header shape is pinned with
--no-color --no-ext-diff --src-prefix=a/ --dst-prefix=b/, so a personal gitconfig carryingdiff.noprefix,mnemonicPrefix,color.diff=alwaysordiff.externalcan no longer throw out through the provider; an unparseable stream degrades to "no diff" (#289) - search: replace writes atomically (temp plus
ATOMIC_MOVE), so a crash mid-write cannot leave a truncated source file with no backup, andcomputeReplacedno longer caps at 500 - a size-bounded file is replaced in full rather than left half-transformed with a success report (#289) - search:
resolveFileproject-confinement is now covered by tests - absolute-outside,../traversal, name-prefix sibling and symlink escape - the control that stopsproject_replacewriting outside the project (#289) - tabs:
TabTypePluginsregistersdiffandcomposer, so opening either with editor-tab absent shows the install prompt instead of hanging fifteen seconds and silently dropping the request (#289) - plugins: the retirement sweep folds the standalone git-status and git-log plugins into their replacement, and
RetiredPluginIdscarries aminReplacementVersionso both offer surfaces hide a plugin whose replacement is not yet new enough (#289) - panel: the panel-fit measurement is gated on the configuration that would actually use it. Naming a column is what makes the subcomposition compose, so with the top bar up - the default - a right-panel drag-resize paid for one measure per frame to answer a question nothing read (#319)
- browser: the favicon fetch caps a reply at 256 KB, declared and actual; it was the only thing between the network and a
ByteArrayon the heap. Cache reads no longer bump mtime, so the most-shown entries stop being the ones eviction can never reach, and entries expire after a fortnight (#318) - browser: each favicon source is attempted independently -
resolvewrapped both in onetry, so a throw from the page-icon source skipped the host guess entirely (#318) - browser: momentum-phase scroll is measured rather than assumed. A
CGEventtap shows macOS emitting it for 180-870ms after the fingers lift, carrying 325-2500px of horizontal travel; with the vertical tiers no longer asked past the commit line, a tail can cost latency and nothing else (#301) - tests: 47 tests across the placement table and the two new layouts at their narrowest and shortest, with the fit guard mutation-checked (#319); the swipe suite gained hold-then-release, ease-back-cancels and pagehide-mid-swipe, and both cross-language constants are read out of the other file rather than restated (#301); the favicon work added the backslash authority, the future timestamp, the entry that outlived its delete and concurrent writes leaving one whole entry (#318)
Known Limitations
- The collapsed rail has no fit guard of its own. Below roughly 283dp of rail height its actions are squeezed out entirely - the same failure class the panel foot now guards against, but the rail's height is the window's rather than a draggable sliver, so closing it means porting the measure-and-report machinery to a second host. Filed as #320 with the numbers and the shape of the fix (#319)
- Whether Chromium forwards momentum-phase scroll to the renderer is still unconfirmed. Synthetic phase-tagged events cannot settle it -
CGEventPostfrom another process never reaches the layered native browser surface - so it needs one real flick against a recordingwheellistener. It cannot change the answer a swipe reaches, only when it arrives (#301)
Dependencies
- boss-plugin-api 1.0.85 to 1.0.87 (#289)
- compose-multiplatform 1.11.1 to 1.12.0 (#286)
- supabase 3.7.0 to 3.8.0 (#285)
- org.xerial:sqlite-jdbc 3.53.2.1 to 3.53.4.0 (#287)
- org.graalvm.buildtools.native 1.1.10 to 1.1.11 (#288)
- actions/setup-java 5.6.0 to 6.0.0 (#292)
- actions/cache 4 to 6 (#293)
馃摝 Downloads
| Platform | Architecture | Package |
|---|---|---|
| macOS | Universal (Apple Silicon + Intel) | BOSS-9.5.7-Universal.dmg |
| Windows | x64 | BOSS-9.5.7.msi |
| Windows | ARM64 | BOSS-9.5.7-arm64.msi |
| Linux DEB | AMD64 (x86_64) | BOSS-9.5.7-amd64.deb |
| Linux DEB | ARM64 (aarch64) | BOSS-9.5.7-arm64.deb |
| Linux RPM | AMD64 (x86_64) | BOSS-9.5.7-amd64.rpm |
| Linux RPM | ARM64 (aarch64) | BOSS-9.5.7-arm64.rpm |
| Linux JAR | AMD64 (x86_64) | BOSS-9.5.7-amd64.jar |
| Linux JAR | ARM64 (aarch64) | BOSS-9.5.7-arm64.jar |
Always latest - newest stable release, resolved server-side, so these stay
correct in a bookmark and need no API key:
macOS DMG 路 Windows x64 路 Windows ARM64 路 Linux DEB amd64 路 Linux DEB arm64 路 Linux RPM amd64 路 Linux RPM arm64 路 Linux JAR amd64 路 Linux JAR arm64
Release metadata - version, every asset, sha256 checksums - is at ?app=boss.
Full Changelog: v9.5.6...v9.5.7