Skip to content

Persona QA fixes: crash, clipboard, keyboard, contrast, config reload, doctor, Raycast extensions, Bazaar icon - #256

Open
hanthor wants to merge 55 commits into
mainfrom
claude/modest-bell-xqa0vv
Open

hanthor wants to merge 55 commits into
mainfrom
claude/modest-bell-xqa0vv

Conversation

@hanthor

@hanthor hanthor commented Oct 1, 2026

Copy link
Copy Markdown
Member

Fixes from a persona QA pass over the real binary on headless Sway: newcomer, design polish, Raycast user, tiling-WM user, and keyboard-first power user. It also covers the follow-ups from #255 and the Bazaar icon. Each fix was checked against before and after screenshots of the real launcher.

Desktop integration

  • The UI no longer crashes on copy while keyboards come and go. smithay-clipboard released its wl_data_device when the keyboard left, and the next data_offer was a protocol error. It is vendored with a patch that keeps one device per seat. compass start now keeps the engine and restarts a failed window, up to 5 times a minute.
  • Unplugging the launcher's output no longer wedges it. layershellev is vendored with a patch so the close is delivered. toggle/show/hide time out after 10 s.
  • The launcher fits small or scaled outputs: an 8 px margin and a 360×240 minimum.
  • Close on focus loss works on layer shell. A backdrop surface catches clicks outside the launcher.
  • Toggling a launcher that is behind another window reopens it focused.
  • Long titles and subtitles end in an ellipsis.

Clipboard and input

  • One Enter no longer submits a form twice. This affected script, quicklink, alias, shortcut and extension forms.
  • Copies survive the window hiding. IPC v24 adds Request::CopyText, so the engine copies over data-control or the Shell helper. The calculator copies the bare value, as upstream does.
  • Forms focus their first field. Tab cycles the fields; Space works checkboxes and dropdowns.
  • Shortcuts.
    • Ctrl+B works in Search Files.
    • An advertised shortcut runs without the panel open.
  • Search history. Up/Down keeps the query you were typing.
  • Deeplinks. compass deeplink … and a bare link both work.
  • Quicklink arguments are percent-encoded (PARITY Shortcuts row 11).
  • Full-output scripts. The output scrolls from the keyboard, Copy output copies plain text, and a failed run shows its exit code.
  • Ctrl+X in Clipboard History deletes the selected entry after a reload.

Keyboard, accessibility, discoverability

  • Settings and onboarding are fully keyboard-driven. A visible accent focus ring (adwaita::focus_ring) shows where you are.
  • Palette::accessible() brings every theme to WCAG AA, with a test over every theme. Examples:
    • selected row: 3.77 → 4.77
    • placeholder: 3.03 → 6.58
    • focus ring: 1.87 → 4.77
  • Settings search finds individual settings.
  • The root footer shows the main action and "Actions Ctrl+B".
  • Escape clears the query first, keeping the search focused, and hides only from an empty query.

Config, doctor, wording

  • compass.json reloads live in both processes.
    • A wrong-typed value falls back to its default and is kept in the file.
    • Unknown keys get a "did you mean".
    • Writes keep comments and layout.
    • The schema rejects unknown keys.
  • compass doctor.
    • Correct on Sway, Hyprland and niri.
    • No more flaky portal FAIL on first activation.
    • New keyring and clipboard-history checks.
  • Onboarding. --hidden waits for the first show, Escape records it as seen, and migrated Vicinae homes skip it.
  • Vicinae migration. vicinae.json is translated with a backup, and pop_to_root_on_close becomes "Clear the search on close".
  • Wording.
    • Store names are distinct, and the Vicinae Store is labelled third-party.
    • Super is spelled out.
    • On Sway the hotkey row reads "Set in Sway", gives the config line, and is skipped by Tab.
    • Under a Flatpak install, the instructions give the Flatpak command.
  • Script commands. @compass.* headers are accepted, a missing header gets a clear error, and Rhai folders no longer cause warnings.
  • Systemd. ExecStart uses the real prefix, and the docs cover starting at login.

Extensions and the follow-ups from #255

  • Extension views survive idling. The UI waits longer than the engine's long-poll hold (EXTENSION_VIEW_HOLD plus a margin) and re-polls on timeout.
  • Raycast extensions owned by an org open and install. StoreEntry::owner is new in IPC v23.
  • Crash messages are real, and a no-view command's failure shows in the HUD.
  • Calculator Ctrl+B panel: Copy Result, Copy Question And Answer, Put answer in search bar, Open Calculator History.
  • Onboarding.
    • Links say "Opened in browser".
    • Adwaita buttons.
    • Raycast picks: Google Translate, Google Search, GIF Search and Tailwind CSS.
    • The theme picker repaints the page.

Flatpak / Bazaar

The OCI appdata label now carries <icon type="remote"> URLs. Bazaar reads cached icons only from icons/flatpak/<size>/, which OCI remotes don't populate. Adds scripts/flatpak/appstream-icons.py (add-remote, check-oci, check-build) and the committed 64 and 128 px icons.

Validation

  • cargo fmt --check, cargo clippy --workspace --all-targets -D warnings and RUSTDOCFLAGS=-D warnings cargo doc --workspace --no-deps: clean.
  • COMPASS_REQUIRE_RUNTIME=1 cargo test --workspace --no-fail-fast: 4808 passed, 0 failed, 2 ignored, after rebuilding the extension runtime and compass-sandbox. compass-ui was re-run after the final two fixes.
  • Live smoke test of the merged binary on headless Sway:
    • root search and its footer
    • the calculator panel
    • typing after Escape clears the query
    • Settings on Sway: Tab skips the guidance row
    • 100 ft to m → Enter → wl-paste gives 30.48 after the window hides
  • The action audit inventory is regenerated.

Not in this PR

  • compass.json docs link 404s. The URL is correct; the cause is tuna-os/docs' sync-org-docs build failing in checkSidebarsDocIds.
  • Copy fallback. Without the engine, or on GNOME without the Shell helper, a fallback copy still dies with the window. Fixing that needs an iced_layershell/winit change.
  • Vendored patches. The two vendored patches (smithay-clipboard, layershellev) should be offered upstream.
  • GNOME tiers. The toplevel fit and reopen-on-toggle need the t2/t3 runs on GNOME.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn


Generated by Claude Code

claude added 30 commits October 1, 2026 09:23
Bazaar resolves a cached AppStream icon only at
<appstream dir>/icons/flatpak/<size>/<file> (bz-appstream-parser.c). An
ostree remote's appstream branch links icons/flatpak/<size> to icons/<size>,
but flatpak writes an OCI remote's catalogue from the image labels into
icons/<size>/ only (flatpak_oci_index_make_appstream), so on the TunaOS
remote every cached icon is skipped and Compass shows a blank tile. Mariner
and the other TunaOS apps have the same problem. The only other source
Bazaar reads is a remote icon URL, and appstreamcli compose drops any icon in
the metainfo, so the URL has to be added after the build.

publish-flatpak.yaml now adds <icon type="remote"> 64 and 128 entries to
each image's appdata label. They point at PNGs committed under
packaging/flatpak/icons/ and are pinned to the commit that was built. The
workflow rewrites the config, manifest and index digests, then fetches the
URLs and checks that they serve the label's PNG. flatpak.yaml checks the same
thing on every build: the composed AppStream has cached 64/128 PNGs, they
match the committed ones, and the OCI image made the publish way carries
both the icon labels and the remote URLs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…t picks

Calculator: Ctrl+B over the answer in root search opens upstream v0.29.0's
RootCalculatorSection::actionPanel: Copy Result, Copy Question And Answer,
Put answer in search bar, Open Calculator History (no "Copy unformatted
answer": fend prints one form). The action audit lists and runs them, counts
text put in the search bar as an effect, and fails when a kind of root row
opens no panel. Over a short list the card grows to hold the whole panel
instead of cutting it off.

Onboarding: the link buttons say "Opened in browser" on the card, or why not
(including without an engine); a step's notice clears when the step changes.
The theme picker is covered by a paint test. Continue and Finish use the
Adwaita suggested action, Back and the link buttons the neutral button,
Install and Try Again neutral, Installing and Installed flat; the boxes are
boxed lists and the dropdown Adwaita's.

Recommendations: four Raycast store extensions that render in Suite 1 and
need no account, key or AppleScript (Google Translate, Google Search, GIF
Search, Tailwind CSS) and three Vicinae ones, each labelled with its store.
An install reports through the store detail page's store_changed. Tests check
Suite 1's verdict, its corpus's author for each, and that none is redirected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Every text field in a form had on_submit(PreferencesSubmit), and the key
handler also turns Enter into PreferencesSubmit. listen_with sees the
event whether or not the field captured it, so one Enter submitted twice:
shortcuts saved twice, scripts and quicklinks ran twice, and Set alias
saved and then launched the selected row. The key handler is now the one
path; the fields no longer submit.

Simulator tests press Enter once in each form (shortcut, quicklink
arguments, script arguments, extension arguments, alias, snippet) and
assert exactly one submit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
A newly opened form (Set alias, extension and script arguments, the
snippet and shortcut forms, ...) left the keyboard nowhere, so typed text
was lost and Enter submitted the empty form. The form now tracks which
field the keyboard is on, and the update that opens one focuses its first
field, whichever path opened it. Tab and Shift+Tab move through every
field, including the ones Iced cannot focus: a checkbox (Space ticks it)
and a dropdown (Space and the arrows step it), each drawn with a ring
while the keyboard is on it. That makes the snippet form's "Expand as
word" reachable.

The arguments form said "Enter: save and run"; arguments are this run's
and are not saved, so it says "Enter: run".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
… P-10)

Theme::palette now passes each palette through Palette::accessible, which
moves a colour toward black or white only as far as it takes
(compass_core::contrast::ensure_contrast): text, secondary text and the
accent reach 4.5:1 against the card, the field and the boxed lists, and
the selection's fill reaches 4.5:1 against the text on it. Palette::control
is the hairline pushed to 3:1, for what marks out a control: an entry's
frame, a switch's off track, the onboarding dots, a font tile's outline.

Adwaita selects in #1c71d8 (white on it 4.77:1, was 3.77:1 on #3584e4),
the dark accent is #78aeed and the dark secondary text #a3a29f (4.73:1 on
a boxed list, was 4.24:1). The search placeholder is the secondary text
colour (was Iced's faded text, 3.03:1 in light); a disabled button keeps
a readable label; onboarding draws its buttons and dropdown the Adwaita
way. A test checks every curated theme in both appearances and every file
in extra/themes against the thresholds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…ot focus

adwaita::focus_ring and adwaita::ringed draw Adwaita's outline: a 2 px
line in the accent, 2 px out from the control, so it shows around a
switch that is itself filled with the accent. The room is kept whether or
not the control has the keyboard, so nothing moves when it does. The
accent is held at 4.5:1 against every background by the palette layer;
the old ring was the accent at half opacity, 1.9 to 2.2:1. An entry's own
ring is the full accent too.

crate::focus is the walk Tab and Shift+Tab take over a page's controls in
the order they are drawn, back to the search field past either end, for
the pages (Settings, onboarding) whose switches, dropdowns and buttons
Iced 0.14's focus_next skips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Both processes watch compass.json and apply a change at once, so hand
edits and `compass theme set` no longer wait for a restart. A value of
the wrong type is set aside on its own (serde_path_to_error) instead of
discarding the whole file, and kept as written; unknown keys and unknown
theme, preset, colour scheme and keybinding names are reported with a
"did you mean" in the engine log, `compass doctor` (new config.file
check) and the settings view. The published schema closes its sections
with additionalProperties: false. Writers edit the file in place through
jsonc-parser's CST, keeping key order, indentation and comments, and CLI
and engine writers no longer replace an unreadable file with defaults.

The settings view puts a switch back when its write is refused and draws
the error in Adwaita's error colour rather than the link colour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Anything the launcher copied (the calculator's Enter, emoji, Copy name,
Copy Deeplink, ...) was lost as soon as the window hid. iced::clipboard
puts the source on the window's Wayland connection, and iced_layershell
(remove_compositor) and iced_winit both drop it with the last surface.

Copies now go to the engine (Request::CopyText, protocol v23), which
sets them over data-control on wlroots and through the GNOME Shell
extension elsewhere, the way pasting already did. Where the engine has
no clipboard the window copies through Iced as before, which holds while
the window does.

Copy name, Copy path, Copy Deeplink and Copy ID now hide with the
"Copied to clipboard" HUD, as CopyToClipboardAction does. The
calculator copies the value, as the C++ copies answer.text (Numen keeps
the unit apart): 88.0669308675, not "approx. 88.0669308675 EUR".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Tab only reached the text fields: Iced's focus_next skips switches,
dropdowns and buttons, so Record Shortcut, the hotkey button, every switch
and every dropdown needed a mouse. The settings page now keeps its own
focus over SettingsPage::controls, every control the page draws in the
order it draws them, and:

- Tab and Shift+Tab walk them and come back to the search field; once a
  control has the keyboard the arrows do too.
- Space or Enter flips a switch, presses a button (the hotkey, Record
  Shortcut, Preferences, the About links) or opens a dropdown, whose
  options then show under its row, the arrows moving the highlight and
  Enter or Space picking it.
- The page scrolls the focused control (or highlighted option) into view.
- Page Up, Page Down, Home and End scroll the page; Home and End stay the
  field's while text is being edited.
- Escape closes a dropdown, then gives the keyboard back to the search
  field, then leaves.
- The control with the keyboard has the 2 px accent ring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…, RC-14)

A page opened at the scroll offset the previous one was left at, and a
page picked with the arrows below the sidebar's fold stayed out of sight.
Choosing a page by click, by the arrows, by a search or by a deeplink now
snaps the body to its top and scrolls the sidebar to the selected row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
On a wlroots compositor the launcher's key is bound in the compositor's
own configuration, so portal.global-shortcuts now passes there and gives
the exact line (bindsym/bind/spawn, with the Flatpak command inside the
Flatpak) instead of failing, and --check-only exits 0. Sway is found by
SWAYSOCK, Hyprland and niri by their own variables, and the desktop check
says so instead of "not a GNOME session ... Phase 5". The wlroots check
no longer counts the compositor binding as a missing hotkey.

New checks: clipboard.history (data control, or the Shell extension on
GNOME) and keyring (Secret Service present, unlocked, with what to
install and how to start it on Sway). A portal that is still starting on
the first run is asked again after a short grace instead of failing.

Removed internal notes from user text: Phase 5, the C++ helper/engine,
PLAN.md, #118 and the --engine flag (now hidden). The engine no longer
logs "window switching unavailable" without a session bus off GNOME.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…s well

F-6: after a compact or inline script ran from its form or the panel, the
search field no longer had the keyboard, so typing went nowhere, and Enter
launched the hidden selected row, running the script again. The search
field now gets the keyboard back when a one-line run starts, and while a
message stands in place of the results Enter puts the results back rather
than launching a row nobody can see. Typing replaces the line.

F-7: the full-output view scrolls with the arrows, Page Up and Down, Home
and End; it takes the card's width, so its scrollbar is at the edge
rather than beside a narrow column; it has Copy output (Ctrl+Shift+C),
which copies the text without its colour codes; and a non-zero exit reads
"Failed after 0.1s (exit code 3)" in red rather than "Done".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Every answer to the history query put the selection back on the first
row, including the reload after a pin, a delete or a new copy. A Ctrl+X
after any of those removed whatever was at the top (often a pinned
entry), not the entry the person had been on. Removal was already by
entry id; the selection is now kept by id across a reload of the same
filter, staying at the same position when its entry is the one removed.
A new filter still starts at the top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
The search field only scored page and provider names, so "quick launch"
or "translucent" found nothing. Each setting's label and description are
now scored too, with compass-search's fuzzy matching (the label counting
twice the description), and a page a setting matched on is listed and
ranked by the better of its name and that setting. The best page is
shown, its matching rows are tinted with an accent outline, the best one
is scrolled into view, and Enter in the search field takes the keyboard
to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
`compass start --hidden` runs at login, so a due first-run flow now
waits until the launcher is first shown instead of opening a window at
startup. Closing the flow with Escape or the hotkey records it as seen,
so it is not shown again at every login, and Finish leaves the launcher
open at its search. A home carried over from Vicinae (the vicinae
directory or its symlink, or Vicinae's settings.json) skips the flow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
The flow took Enter and Escape and nothing else, and Escape hid the
launcher from any step. Each step's controls (the theme dropdown, Open
Docs, each Install that can be pressed, GitHub) and then Back and
Continue are now a Tab order, with Space or Enter to press the focused
one and the 2 px ring on it; the theme dropdown opens from the keyboard
as a list under its row that the arrows move through. Escape closes that
list, else goes back a step, and closes the flow only from the first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
With text in the root search, Escape hid the launcher and kept the text
for the next summon. Upstream Vicinae v0.29.0's
NavigationController::goBack at the root search closes the window only
when the search text is empty and otherwise clears it, as Raycast does;
Compass now does the same. The Keybindings page says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
A store listing, an extension's Markdown detail, a store's introduction
and Create Extension's result page scrolled only with the wheel: the
arrows moved a selection there is none of. Up and Down now scroll them by
a line, Page Up and Page Down by most of the card, Home and End to either
end (scroll::reading_key).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
F-10: Search Files' and Switch Windows' key branches took every key and
sat above the Ctrl+B check, so Ctrl+B never opened their panels (and with
a panel open the arrows still moved the list). They now step aside for
the panel's keys, and Search Files' panel is the file actions upstream
v0.29.0 offers (Open, Show in file browser, Open with..., Copy file,
Copy file path, ...).

F-12: a panel advertised shortcuts (Copy Deeplink Ctrl+Shift+C, Copy file
Ctrl+Shift+C, Run again Ctrl+R, ...) that did nothing unless the panel was
open, and some not even then. A chord now runs the action that advertises
it, over the open panel or over the panel Ctrl+B would open, for every
panel; the keybinding scheme's movement chords keep their meaning, and a
chord no action claims changes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Up (or the scheme's up chord) at the top of the list replaces the query
with the last search, as upstream does, but what was being typed was lost
and Down only moved the list. The query being typed is now kept when the
walk starts, and Down steps back towards the newest search and then puts
it back (root_view::newer_history_entry).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
`compass deeplink compass://launch/commands/clipboard-history` was refused
by the CLI's own pre-filter, which knew the OAuth redirect, settings and
store links but not the launch links the engine's OpenDeeplink opens
(parse_launch_link). It now forwards those too.

The bare `compass <url>` form the help promises only worked with the URL
as the very first argument; with a global option before it
(`compass --socket <path> <url>`) the URL was taken for a subcommand and
the CLI exited 2. The first argument that is not a global option is now
the one looked at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
A compass.json moved from vicinae.json still holds Vicinae's keys, which
nothing read. The engine now translates them at start the way
settings.json is translated, keeping Compass's own keys and the original
as compass.json.vicinae.bak. `compass config migrate` also looks in
~/.config/vicinae before Compass has run once.

pop_to_root_on_close is carried over and implemented as
launcher.pop_to_root_on_close ("Clear the search on close" in Settings),
off by default as in Vicinae v0.29.0's default config. font and
favicon_service, which Compass reads as Vicinae writes them, are carried
over instead of being listed as left behind.

The theme note names the light theme as it was written, a chosen theme
wins over a default one whichever variant it was set for, and a default
light theme is no longer reported as dropped. The "using the migrated
settings" line is logged once per process instead of on every read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
expand() put argument values (and the clipboard and selection) into the
link as typed, as upstream does, so `?q={query}` with
`is:open label:bug & more` searched for `is:open label:bug ` and sent
` more` as a parameter of its own. In a URL template (a scheme and ://
before any placeholder, not file:) each value is now percent-encoded,
keeping only RFC 3986's unreserved characters and `/`. A link that is all
placeholder, a path or a command line is filled in as before. Recorded
in PARITY (Shortcuts, row 11).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…nd badges (F-11, m8, RC-14)

The root search's status bar held only the clock, so the action panel
was found by accident. It now carries what Enter does to the selected
row ("Open", "Open command", "Run script", ...) and "Actions Ctrl+B", the
footer dmenu already draws, with the clock kept as its title.

Shortcut badges showed the binding as stored, "enter" next to "Ctrl+C";
key_combo::badge reads every spelling the same way: Enter, Ctrl+Shift+C,
Ctrl+Shift+arrow. Action titles mixed title and sentence case, as
upstream's do; they are now all sentence case, the case most of upstream
v0.29.0's action titles use (42 of 74 multi-word ones), with acronyms and
names kept. ACTION-AUDIT.md is regenerated for the new titles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
- The Vicinae Store is named as the third-party store it is (command
  subtitle, intro, search placeholder, detail titles); the Raycast intro
  says what "these limitations" are; Raycast detail pages are titled
  "Raycast Store - ..."; About no longer says "compatible with Vicinae".
- The theme template is no longer "Vicinae Dark" with a docs.vicinae.com
  link; getting-started gains a Themes section it points to.
- Settings' "Not in Compass" rows and errors lose developer notes (Iced,
  COMPASS_LAYER_SHELL, "a hard fork", Qt, "the engine log says which",
  "the engine has no session bus"); folder fields say what separates
  them; Color, not Colour; sentence case for labels.
- CLI help and schema descriptions lose #153, C++, Qt and ADR notes.
- The power confirmation asks a question and has a whole sentence.
- Hibernate's description no longer contains "restored", so it does not
  answer "store"; an item the search already lists is not offered again
  as a fallback ("docs" listed Search Files twice).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
The ring around a dropdown the keyboard is on was a padded container,
which set the dropdown in from the text fields above it. It is now the
dropdown's own border, so it lines up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
The Super modifier is written "Super" instead of the ◈ glyph. On Sway,
Hyprland and niri (found by their own socket variables) the settings
view no longer offers a hotkey recorder whose key nothing would bind: the
row says "Set in Sway" and gives the line for the compositor's
configuration. Inside the Flatpak (/.flatpak-info) every instruction
uses `flatpak run org.tunaos.compass toggle`. Onboarding's hotkey row
and its last step say the same, and the last step says that Finish
opens the launcher. The docs give the Flatpak bindings too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
… off the card

With every theme file listed, the list opened from the keyboard grew the
"Make it your own" step past the card's fixed height. It now scrolls past
five themes, and the arrows keep the highlighted one in view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
With nothing typed the suggestions fill the card, and the new status bar
(and the clock before it) was pushed off its foot. The list now scrolls in
the room the card has left after the field and the bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
`@compass.*` header keys are accepted as Compass's spelling of
`@vicinae.*` (they may be used together, not with `@raycast`), so a
script written for Compass no longer fails with "Invalid schema
version, expected 1"; a file with no header at all says so. Rhai
scripts in the same scripts directory (a folder with script.toml) are
left to the Rhai tier instead of failing as script commands, and a broken
script is warned about once per process instead of at every rescan. A
script directly in the scripts folder is labelled "Script" rather than
"scripts". `compass script template` says on stderr where to save
the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
claude added 22 commits October 1, 2026 11:25
… install

Blocker 1: every extension view died about two seconds after its last
render with "Reading the view timed out". The engine holds an
ExtensionView read for up to 20 s waiting for a change, but the window
asked with the 2 s REQUEST_TIMEOUT, took the silence for a failure and
stopped polling, so typing, actions, push/pop, consent and OAuth went
nowhere. The hold is now compass_ipc::EXTENSION_VIEW_HOLD, shared by both
sides, and the client waits long_poll_deadline(hold) = hold + 5 s. An
answer that comes back unchanged is a re-poll, not a redraw. The same
mismatch is fixed for RunExtensionCommand (the engine waits up to
LOAD_TIMEOUT = 10 s for the runtime) and ProbeShortcut (PROBE_TIMEOUT
= 3 s). Rhai script views read through the same request and are fixed by
the same change. COMPASS_EXTENSION_VIEW_HOLD_MS shortens the hold for
tests, never lengthens it.

Blocker 2: the Raycast store built /extensions/<author>/<name>, and the
API files an organisation's extension under its owner (raycast/github is
200, thomaslombart/github is 404). Listings now read `owner`; detail,
install, deeplinks and the marker use it, the author is still shown. IPC
v23 adds StoreEntry.owner and renames the StoreExtension/StoreInstall key
to owner. Installed ids (store.raycast.<name>) carry no handle, so
existing installs are untouched. raycast://extensions/<owner>/<name>/<cmd>
now runs the installed command, and ownerOrAuthorName is the manifest's
owner when it has one.

Also:
- A crash carries its real reason: the generated RPC client rejects with
  an Error (not a bare string), the runtime reports error.message rather
  than an undefined `stack`, and the engine falls back to the refusal it
  just sent when a crash says nothing.
- Store list errors show above the rows, not below a full list.
- A no-view command's failure toast or crash reaches the launcher
  (WindowCommand::Failure): the reason in the HUD now, and the error
  line when the launcher is next shown. showHUD uses the launcher's HUD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
install-rust-engine.sh writes the installed binary's absolute path into
compass.service's ExecStart (systemd only searches its own directories
for a bare name) and checks that it did. getting-started gains a
"Start Compass when you log in" section: the unit needs
graphical-session.target and WAYLAND_DISPLAY in the user environment,
which plain Sway provides only with uwsm, sway-systemd or an
import-environment line and a session target. The Privacy section now
names the daily ECB exchange-rate download and how to turn it off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…ow are

On an unnamed desktop a missing GlobalShortcuts portal warns; a portal
D-Bus can start on the private bus is reported as started; on a real
headless Sway the wlroots and global-shortcuts checks pass. The config
CLI tests expect pop_to_root_on_close carried over and the new wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…rt a failed launcher (TIL-01)

A copy in another client while the launcher was up killed the UI with "not a valid new
object id ... data_offer(n)", and under `compass start` the engine with it.

Root cause, from WAYLAND_DEBUG: smithay-clipboard (window_clipboard, under both iced_winit
and iced_layershell, on the UI's own wl_display) releases its wl_data_device when the seat
loses its keyboard, and its Drop releases it too, which both shells trigger on every hide.
A data_offer the compositor had already sent then reaches the zombie device; libwayland
1.22 discards it without reserving the server-allocated id, and the next data_offer fails
the id check, a fatal protocol error. Each wtype run is a keyboard coming and going, which
is why the QA harness hit it.

- vendor/smithay-clipboard: 0.7.3 patched to keep a device for the seat's life and one
  worker per display for the process (Clipboard is a handle to it). A copy made just before
  the launcher hides now survives the hide too.
- `compass start` keeps the lease and the engine and runs the window as a hidden
  `launcher-child`, restarting it (hidden) when it fails, up to five times a minute; the
  child ends when start goes away.
- The UI's errors say "could not start" only for a start failure, and "stopped
  unexpectedly" when the loop dies under a running launcher; an iced_winit loop that ends
  without the app asking (how it reports a dispatch error) is now a failure.
- wlroots tests: copies racing keyboards that come and go (fails 5/5 without the patch),
  and a killed launcher coming back with the engine kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…ds (TIL-02)

Unplugging the output the launcher was on left `toggle`, `show` and `hide` blocked
forever.

Root cause: layershellev's output_destroyed took the surface's unit out of its list (the
unit had the output from wl_surface.enter, even with OutputOption::Active) before the
Closed for it was handled, so iced_layershell could not map the close to a window and
dropped it. The app waited for a Closed that never came, and the engine holds its window
slot while it waits, so every later command queued behind it.

- vendor/layershellev: 0.19.1 patched to ask for the close instead, which goes through the
  ordinary path while the unit exists and then destroys the surface. The next show opens a
  new surface on the current output.
- `compass toggle/show/hide` give up after 10 s with a clear error rather than hang.
- wlroots test: show on a second output, unplug it, then toggle/hide/show answer and the
  launcher process is the same one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…e; toggle a launcher left behind back (TIL-04, TIL-03)

TIL-04: the fixed 768x608 window did not fit 1366x768 at 150 % (911x512 logical),
1280x800 at 200 % or 1920x1080 at 200 %, and compositors centre an oversized surface, so
the search field went off the top. A layer surface now opens anchored to every edge with no
size, learns the room from its first configure, and is given the launcher's size fitted to
it (`surface::fit`: 8 px margin, never below 360x240), drawing nothing until then. A
toplevel asks for its monitor once mapped, is shrunk to fit and opens fitted after that.
The list scrolls in the shorter window.

TIL-03: a layer surface holding the keyboard exclusively never loses the focus, so close on
focus loss never fired on Sway and a click went through to the window underneath. With the
option on, a transparent backdrop layer surface now sits under the launcher and a click on
it hides the launcher; its input region leaves the launcher's rectangle out, so a click on
the launcher reaches it whichever surface the compositor stacks on top. On the toplevel
path, `toggle` on a launcher left behind another window (option off) brings it back as a
new window, which the compositor focuses, instead of hiding it; a launcher that never
reported the focus (a seat with no keyboard, as in the QA harness, where Focused was never
recorded) still toggles closed.

Tests: fit, the backdrop region, the toggle cases, the monitor fit and the backdrop click
(t0); the launcher in a window fitted to 640x400 keeps inside it and scrolls (paint tier).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…ancelled (P-03)

Escape on "Are you sure" for a power command returned Task::none(), leaving the field
without the focus; it now returns focus_search(), as the generic confirmation does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
List rows are a fixed height and Iced's text wraps, so a long subtitle (LibreOffice's
comment, store and extension descriptions) took two lines and ran into the row below.
`compass_ui::elided` shapes the line with the renderer's own paragraph at layout and draws
the longest prefix that fits with an ellipsis; `list_row_parts`, which the root, store,
extension and Compass settings lists all draw their rows with, uses it for the title and
the subtitle. The Adwaita-style settings rows grow with their description rather than
overlap, so they keep wrapping.

Tests: elide's cut points (t0) and a long subtitle laid out one line tall inside its row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…ll-xqa0vv

# Conflicts:
#	crates/compass-core/src/onboarding.rs
#	crates/compass-ui/src/app.rs
…ll-xqa0vv

# Conflicts:
#	crates/compass-ui/tests/paint.rs
…ll-xqa0vv

# Conflicts:
#	crates/compass-ui/src/adwaita.rs
#	crates/compass-ui/src/app/onboarding.rs
…ll-xqa0vv

# Conflicts:
#	crates/compass-ipc/src/protocol.rs
#	crates/compass-ui/src/app.rs
#	docs/rust-engine/ACTION-AUDIT.md
…ll-xqa0vv

# Conflicts:
#	crates/compass-ui/src/app.rs
#	crates/compass-ui/src/app/onboarding.rs
#	crates/compass-ui/src/app/settings_view.rs
#	crates/compass-ui/src/app/settings_view/tests.rs
#	crates/compass-ui/src/design.rs
#	crates/compass/src/serve.rs
#	docs/rust-engine/ACTION-AUDIT.md
#	docs/rust-engine/PARITY.md
…rated action audit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…ocused

On Sway, Hyprland and niri the hotkey row is guidance, so Tab no longer
lands on a recorder that is not drawn. Clearing the query with Escape
handed focus back to nobody, because the text field drops it on Escape,
so the next keys were lost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
claude added 3 commits October 1, 2026 12:21
…ace, closed schema

The config schema now rejects unknown keys, which the reader keeps but
reports, so the schema check expects them flagged. The seam check's
isolated workspace copies vendor/, which [patch.crates-io] points into.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
… on the renderer

crane's buildDepsOnly stubs every Rust source, the patched crates in vendor/
among them, so clipboard_wayland built against an empty smithay_clipboard.
On the runner the new window spent 8.7 s probing Vulkan before GL, past the
10 s the two new Sway tests gave their first Show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Each wtype adds a keyboard and removes it, which left the seat with none.
The toplevel then lost focus with no other window to take it, and toggle's
behind-another-window rule reopened it instead of hiding it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants