Skip to content

Rebrand to Compass (org.tunaos.compass); Raycast extensions on Linux; clickable rows and a transparent window - #234

Merged
hanthor merged 18 commits into
mainfrom
claude/modest-bell-xqa0vv
Sep 25, 2026
Merged

hanthor merged 18 commits into
mainfrom
claude/modest-bell-xqa0vv

Conversation

@hanthor

@hanthor hanthor commented Sep 25, 2026

Copy link
Copy Markdown
Member

Three pieces of work, tested together. A full cargo test --workspace on this exact tree passes: 4,678 passed, 0 failed. fmt and workspace clippy -D warnings are clean.

1. Rebrand to Compass (ADR-0020, the Phase 7 cutover)

New names

Old New
App ID (Flatpak, desktop, metainfo, icon, tray) com.vicinae.Vicinae org.tunaos.compass (metainfo <replaces> the old ID)
Executable / crate vicinae compass (crates/compass)
Helper programs compass-file-indexer, compass-input-server
Config ~/.config/vicinae/vicinae.json ~/.config/compass/compass.json
Data, cache and state …/vicinae …/compass
Socket $XDG_RUNTIME_DIR/compass/ipc.sock
Env vars VICINAE_* COMPASS_* (the old names are still read, with a one-time deprecation warning)
URL scheme vicinae:// compass:// (vicinae://, raycast:// and com.raycast:// are still accepted)
D-Bus org.tunaos.compass.WindowTracker; Shell contract org.tunaos.compass.Shell.{Windows,Clipboard} at /org/tunaos/compass/Shell/*
GNOME Shell extension compass@tunaos.org
systemd unit compass.service
Layer-shell namespaces compass, compass-hud

Migration. On compass serve, the engine moves vicinae into compass for the config, data, cache and state directories, and leaves a relative vicinae → compass symlink so a rollback still works.

  • If a compass directory already exists (pre-cutover builds made some), the engine moves in only the entries it lacks and never overwrites. When a name exists on both sides, it keeps a real vicinae directory and logs a warning.
  • vicinae.json becomes compass.json the same way.
  • There are 19 unit tests on temp dirs, plus an end-to-end test that starts a real engine over a pre-rename install.

Builtins

  • "Report a Compass Bug" goes to this repo's issues.
  • The upstream Discord command becomes "Compass on GitHub".
  • Onboarding and settings docs links go to tunaos.org/compass.
  • Onboarding shows the Compass logo.
  • "Sponsor Upstream Vicinae" is kept as credit.

What keeps the name vicinae, and why

  • @vicinae/api: third-party extensions import it.
  • The Vicinae Store and its store.vicinae.* IDs: an upstream third-party service.
  • Importer formats (the keyring labels).
  • The upstream vicinae-hotkey-v1 protocol.
  • Credit, and the benchmark against upstream v0.29.0.
  • Prose about the C++ reference.

Packaging. The manifest, desktop and metainfo files, schema, nix, Arch, AppImage, workflows (including publish), VM tier, suites and benches are all renamed. The benches still launch the pinned upstream AppImage under its own name.

2. Raycast extensions on Linux

  • A runtime shim for macOS-only calls:
    • open becomes xdg-open, and pbcopy/pbpaste use the clipboard.
    • Homebrew paths map to Linuxbrew.
    • AppleScript and other macOS-only programs are refused by name.
  • A consent-gated host-command broker instead of widening the sandbox. The prompt reads "Allow Brew to run brew?" with Allow Once, Always Allow or Deny. Grants are stored in host-command-grants.json, and Script Permissions lists and revokes them.
  • A curated overrides manifest. Raycast's real Brew extension runs against Linuxbrew.
  • Measurements. The design and the survey of macOS-only signals in the top 300 extensions are in docs/rust-engine/RAYCAST-LINUX-SHIM.md.
  • Protocol. IPC v22.

3. Two UI bugs

  • Clicks on root-list rows did nothing. Only the root list's rows lacked a mouse_area. A single click now selects and activates a row, the same as Enter, and focus returns to the search field. Hovering doesn't move the selection, as in the C++. Simulator tests click an unselected row.
  • A light rectangle filled the window around the card. No entry point set a program style, so iced cleared every frame to the theme background. LauncherApp::style() now clears to transparent in all three entry points, and the HUD is fixed too. A new paint-tier test asserts alpha 0 outside the card's shadow, on wgpu and tiny-skia. The fix was also checked by hand on headless Sway in both the layer-shell and xdg_toplevel presentations.

Removing the C++ tree follows in its own PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn


Generated by Claude Code

iced's default scrollbar is a 10px square rail and scroller. Every
scrollable now goes through crate::scroll::scrollable, which draws the
C++ ViciScrollBar: 6px wide, radius 3, no rail, the text colour faint at
rest and stronger under the pointer or while dragging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Every Markdown view (an extension's Detail, the store detail page, the
store intros, the created-extension page) built its settings with
`markdown::Settings::with_text_size(14, &theme)`, whose `style.font` is
`Font::default()` -- the generic sans-serif -- while every other text
widget uses `LauncherApp::font()`. cosmic-text maps generic sans-serif
to a hard-coded "Open Sans"; where that family is missing (a stock
GNOME install) each span goes through its fallback list instead, and
bold spans of a variable default family land on whichever family has a
static 700 face (DejaVu Sans Bold, Cantarell Bold...), so the page's
text was in a different, mixed face from the rest of the launcher.

`LauncherApp::markdown_settings` now sets `style.font` to the launcher
font (code keeps iced's monospace) and all four views use it; the
store viewer's image placeholder uses the same font.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
publish-flatpak.yaml takes the bundle a green Flatpak run on main
already built and smoke-tested, exports it as an OCI image to
ghcr.io/tuna-os/compass and records it in the remote's index through the
org's shared publish-flatpak-index step. It refuses to publish an image
without AppStream labels.

The metainfo gains a developer, screenshots, branding colours and a
first release. The screenshots are rendered through the paint tier by an
ignored test, regenerated with `just screenshots`.

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

Closes the last amber parity cell, src/services/window-material Rust ✓
(152 of 152). The maintainer approved an unsafe exception (ADR-0019):

- compass-wayland-foreign: a Linux-only crate that does not inherit the
  workspace's forbid(unsafe_code); it denies unsafe, restates the other
  lints, and allows it in one function (adopt) with two blocks,
  Backend::from_foreign_display and ObjectId::from_ptr. Its safe API,
  bridge(&window), takes both raw-window-handle handles from one window,
  accepts only Wayland ones, checks the pointer is a wl_surface, refuses a
  surface that is not a wayland-rs proxy, names the client_system backend
  so the wrong one does not compile, and keeps one Connection per display
  for the process's life.
- compass_platform::WindowMaterial (the seam), implemented by
  vicinae::window_material over the bridge and
  compass_wayland::material::BackgroundEffects, handed to
  compass_ui::run_resident by the binary.
- compass-ui measures the card with a sensor keyed on tint and corner
  radius and asks through iced::window::run for the card's rounded
  rectangle while the card is translucent, none when it is not.
- BackgroundEffects drops effects of destroyed surfaces before sending,
  since set_blur_region on one is a protocol error on winit's display.

Under the xdg_toplevel presentation only: iced_layershell drops
window::run, a declared difference. Tested on headless Sway (the bridge)
and an in-process compositor that blurs (the region traffic); real blur
on KWin is VM tier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
The README now leads with what Compass is and a measured comparison against
the pinned, unmodified Vicinae v0.29.0 AppImage: cold start (96 ms vs 1,746 ms
to IPC ready; 0.9 s vs 2.3 s to a populated launcher), keystroke to frame
(56 vs 153 ms), idle PSS (218 vs 263 MiB), 46 vs 136 shared objects and
72 MB vs 310 MB of program files. It also says where Compass loses: its fuzzy
scorer is 2-3x slower per core, and it runs more threads.

- scripts/bench/compare.sh and compare.py (just bench-compare): headless Sway,
  a private D-Bus bus with no activation, throwaway HOME/XDG, both engines
  under unshare --net, alternating runs, process trees found by an
  environment tag.
- scripts/bench/fuzzy/cpp_rank.cpp and compass-testkit's fuzzy-throughput
  bin: the two scorers over the same 10k haystack and queries.
- docs/rust-engine/BENCHMARKS.md: method, machine, raw per-run numbers,
  the SLA benches, and a still-to-measure list. The raw report is archived
  under benchmarks/2026-09-25-compare.
- Install: the TunaOS Flatpak remote (com.vicinae.Vicinae), CI bundle,
  flatpak-builder, the other packages and cargo. Stale migration-status
  prose and Vicinae-only instructions are gone; credit to Vicinae is kept.
- CONTRIBUTING.md points at Compass's tracker, not Vicinae's; CUTOVER.md
  no longer quotes 70/158.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
…mand broker, and an overrides manifest

Measured first: of the top 300 Raycast store extensions, 153 carry a
macOS-only signal (90 AppleScript/JXA, 39 ~/Library paths, 28 `open`,
27 Homebrew prefixes, 0 pbcopy/pbpaste); docs/rust-engine/RAYCAST-LINUX-SHIM.md
has the table and scripts/suite1/macos_signals.py reproduces it.

- The runtime's shim (extension-manager/src/linux-shim): child_process and
  fs behind proxies for the extension's require. `open` runs xdg-open,
  pbcopy/pbpaste use the runtime's clipboard, osascript and other
  macOS-only programs fail by name (CompassRefusal/ENOTSUP) instead of
  ENOENT, Homebrew's macOS paths map to Linuxbrew's. process.platform
  stays linux.
- The broker (HostCommand/run, vicinae::host_commands): `brew`, for any
  extension, runs on the host as the engine's child (flatpak-spawn --host
  inside the Flatpak) once the person allows it: Allow Once / Always Allow
  (Ctrl+Enter) / Deny. Grants in $XDG_CONFIG_HOME/compass/
  host-command-grants.json, listed and revoked in Script Permissions. The
  extension's Landlock policy is not widened. IPC v22 for the alert's
  third answer. The Qt engine refuses the call by name.
- The overrides manifest (extensions/raycast-linux-overrides.json): per
  extension host programs, path and command maps, load-time patches and
  install redirects, read by the runtime and the engine.

Raycast's real Brew bundle renders Show Installed and Search against a
Linuxbrew `brew` behind the sandbox; an end-to-end test covers the same
path with a fake brew. The runtime gets unit tests (npm test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Clicks on the root search list did nothing: its rows were the only list
rows not wrapped in a mouse_area, so a press reached no handler. A click
now sends ResultClicked, which selects the row and does what Enter on it
does, and hands focus back to the search field. Hover still never moves
the selection, as the C++ SelectableDelegate.

The window painted a light rectangle behind the card on both the layer
surface and the xdg_toplevel: no program style was set, so iced cleared
every frame to the theme's background (the card's surface colour).
LauncherApp::style clears to transparent and is wired into run,
run_resident and run_resident_layer_shell.

Tests: Simulator clicks on a root application row (launches that row,
not the selected one), on a root command and then a store row (opens the
store, then that extension), and a hover that must not select; the paint
tier checks the window below the card's shadow is alpha 0 with the app's
style, with the theme's base colour as a failing control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
The Rust side of the Phase 7 cutover (ADR-0012; spec in ADR-0020).

- crates/vicinae is now crates/compass: package, library and binary
  `compass`; the helpers are `compass-file-indexer` and
  `compass-input-server`, looked for in ../libexec/compass or
  ../lib/compass.
- Config, data, cache and state live under `compass`, the config file is
  `compass/compass.json`, the schema `compass.schema.json` (regenerated), and
  the IPC socket is `$XDG_RUNTIME_DIR/compass/ipc.sock`
  (`/tmp/compass-$USER` without a runtime dir).
- On `compass serve`, before anything creates a `compass` directory, each
  `vicinae` base directory is moved to `compass` and left as a symlink. When
  `compass` already exists (the pre-cutover engine kept scripts, grants and
  caches there), the entries it lacks are moved in and nothing is
  overwritten; `vicinae.json` becomes `compass.json` the same way.
- `COMPASS_*` environment variables, with the `VICINAE_*` spelling read as a
  fallback that logs a deprecation once. `VICINAE_API_URL` is
  `COMPASS_VICINAE_API_URL`.
- Compass emits `compass://` and accepts `vicinae://` and `raycast://`.
- D-Bus: `org.tunaos.compass.WindowTracker` for KWin, and the shell
  extension contract is `org.tunaos.compass.Shell.{Windows,Clipboard}` at
  `/org/tunaos/compass/Shell/*`. App id, tray item and icon are
  `org.tunaos.compass`; layer-shell namespaces `compass` and `compass-hud`.
- User-facing strings say Compass. Report Bug files against tuna-os/compass;
  the Discord builtin and tray entry are now "Compass on GitHub", and the
  sponsor entry is labelled as upstream credit.
- The extension runtime is given COMPASS_VERSION/COMMIT and reads the
  `VICINAE_*` names only as a fallback; the SDK dev client dials the new
  socket and emits compass:// links.

Kept on purpose: @vicinae/api, store.vicinae.* ids and the Vicinae store,
the keyring labels the importer reads, the vicinae-hotkey-v1 protocol, the
@Vicinae script-command scope, the vicinae-dark/-light theme ids and C++
references.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
Rename the Flatpak manifest, desktop entries and metainfo to the
org.tunaos.compass ID and ship the `compass` binary, with libexec/compass
and share/compass layouts, across the Flatpak, AppImage, Arch and Nix
outputs. The metainfo <replaces> com.vicinae.Vicinae, the URL handler
takes compass:// alongside vicinae://, raycast:// and com.raycast:, and an
opt-in compass.service user unit is installed.

The GNOME Shell extension becomes compass@tunaos.org with its interfaces
under org.tunaos.compass.Shell.*. The config schema moves to
compass.schema.json and nix/vicinae.nix to nix/compass.nix. CI workflows,
the VM tier, Suite 1/5, tier 2, wlroots and the bench scripts build
`-p compass` and run target/*/compass; the benchmarks still launch the
pinned upstream Vicinae AppImage under its own name.

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

The README, CONTRIBUTING and the Rust engine docs use the new app ID, the
compass command, the compass crate and the compass config, data, cache,
state and runtime directories. The README says an existing
~/.config/vicinae is moved on first start instead of saying the old names
are kept.

ADR-0020 records the rebrand: the new names, what keeps the vicinae name
and why (@vicinae/api, vicinae:// deeplinks, the Vicinae Store, VICINAE_*
fallbacks, upstream credit, the C++ tree), the directory migration, and
why no Flatpak data migration is needed. It supersedes ADR-0012's
compatibility list.

The TypeScript READMEs and package metadata describe Compass. The
@vicinae/api module name stays.

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
The Vicinae store's Linuxbrew extension now reaches brew through the
consent-gated broker, so a headless run sees its 'Allow Linuxbrew to run
brew?' prompt and nothing answers it. Record that as its own verdict, like
needs-sign-in for OAuth, rather than as an empty frame, and expect it in
the ledger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtGVEzDmYTdpsuEQErmmLn
@hanthor
hanthor merged commit 2d0c981 into main Sep 25, 2026
36 checks passed
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