Skip to content

build(deb): ship the iced UI on the adopted GTK profile - #315

Merged
charliek merged 9 commits into
mainfrom
feature/plan-022-deb-swap
Aug 9, 2026
Merged

build(deb): ship the iced UI on the adopted GTK profile#315
charliek merged 9 commits into
mainfrom
feature/plan-022-deb-swap

Conversation

@charliek

@charliek charliek commented Aug 9, 2026

Copy link
Copy Markdown
Owner

What this is

Workstream B of plan 022: the Linux .deb now ships the iced UI as
/usr/bin/roost, adopting the GTK profile in place.
Existing users upgrade
with no migration step — same socket, same state.json, same log dir — so
roostctl and the Claude hooks keep working untouched.

Per the recorded M4 decisions: in-place profile adoption, clean swap, no
beta/opt-in phase. GTK leaves the package; its source stays in the repo and
stays built and tested in CI until the separate retirement decision.

This does not cut a release. The packaging is built so the next tag simply
has it.

Commits

C3 feat(iced) linux-package cargo feature flips the compiled-in default profile to Gtk on Linux only, via a pure function unit-tested over all four cells
C4 fix(iced) window application_id and the notification desktop-entry hint derive from the resolved profile (the Linux half of #303)
C5 build(deb) build-deb.sh builds iced with the feature and stages it as roost; Depends: derived from a runtime trace
C6 ci(release) GTK dev packages dropped; packaged-artifact smoke added
C7 ci(iced) the release lane now builds and asserts the packaging configuration
C8 docs paths / installation / linux/README.md / roadmap M4

The design, and why

ROOST_BUNDLE_PROFILE still overrides the compiled-in default, and every dev
harness (ui.py:481, screenshot/lib.sh, run-iced) pins it explicitly — so
dev and test behavior cannot break, by construction rather than by convention.
Nothing in paths.rs changed: the packaged build resolves an existing
profile kind, so the four hand-maintained copies of the resolver (Rust, Swift,
Python, Bash) are untouched. The single-instance lock then comes out right for
free, because lock_path() derives from the profile's socket dir.

Rejected alternatives: an env var in the .desktop Exec= (covers only the
launcher — shell-launched and roostctl-spawned instances would land on a
different profile, which is the worst possible failure shape); renaming the
[[bin]] to roost (collides with roost-linux's bin name, and wouldn't
change profile resolution anyway).

The dependency set is measured, not reasoned

This is the part that would have shipped broken. readelf -d on a release
roost-iced lists three NEEDED entries — libc, libgcc_s, libm
because winit, wgpu and ash dlopen the entire graphics and input stack. A
conventional ldd-derived Depends: would therefore have been two packages:
it would have built, installed cleanly on every machine, passed every
automated gate, and then failed to launch for anyone whose system didn't
already carry the stack from the old GTK package.

So the list comes from strace -f -e trace=openat of a real launch, mapped
through dpkg -S: 48 packages opened, reduced to the ten the application
itself loads. Mesa's ~30-package closure comes via its own Depends and is
deliberately not pinned.

Vulkan is Recommends:, on evidence. With /usr/share/vulkan/icd.d
removed entirely the app still opens a window, and separately in a container
with no libvulkan1 at all it opened a window with iced_wgpu logging
Available adapters: [] — the software renderer compiled in beside wgpu.
Ordinary apt install still pulls Vulkan (recommends are on by default);
--no-install-recommends degrades to software instead of failing.

Verification

Full evidence: ~/.claude/plans/roost/022-deb-swap/shed-deb/adoption-proof.md.

A real apt upgrade transaction, not a hand-seeded state file. The GTK deb
built from main@ac472ce was installed and run, authoring a 3-project /
4-tab layout with the shipping GTK binary. The iced deb then installed over it:

Unpacking roost (0.0.17~iced1) over (0.0.17~gtkbase) ...

No file conflicts, no prompts. /usr/bin/roost: 7,815,104 → 25,924,712 bytes.

Post-upgrade, with an unmodified roostctl (no flags, no env):

socket=/tmp/rt/roost/roost.sock     # production namespace
ADOPTED_LOG_DIR=/root/.local/state/roost
ADOPTED_STATE_DIR=/root/.local/share/roost
NO_ICED_NAMESPACE=confirmed

…and the GTK-authored layout restored intact, cwds preserved (tab ids are new
because relaunch re-opens tabs as fresh shells — documented behavior).
roostctl tab open through the adopted socket returned a new tab id.

Cross-toolkit single-instance: the old GTK binary launched against the
running packaged iced UI dialed the shared socket, sent app.activate to it,
and exited — one lock, one writer, across two toolkits. A SIGKILL'd instance
leaves a stale socket and lock, and the next launch recovers cleanly.

Desktop identity, read from the real window property under Xvfb:

dev build:      WM_CLASS = "ai.stridelabs.Roost.iced"
packaged build: WM_CLASS = "ai.stridelabs.Roost.gtk"

matching the installed ai.stridelabs.Roost.gtk.desktop and its
StartupWMClass. desktop-file-validate passes.

Regressions — dev profiles untouched (shed, X11):

suite result
iced e2e (wgpu, full list + clipboard) 85 passed, 2 known skips
GTK e2e (full tools/roosttest) 171 passed, 16 skips

GTK's pass count is identical to plan 021's baseline; the skip delta (11→16)
is the five IME tests PR #313 added, which skip on GTK where tab.feed_ime is
deliberately not-implemented.

Mac loop per commit: check-iced clean with and without the feature,
cargo test --workspace, e2e-iced-ci 84 passed / 3 known skips.

Review findings and dispositions

CodeRabbit's adversarial pass on C3 found three real defects, all fixed before
commit:

  1. The new gate was vacuous in CIci.yml never invokes make check-iced, so the packaging feature would still have first compiled during
    a release build, which is exactly what the gate claimed to prevent. The
    --features pair is mirrored into the iced job.
  2. The feature-gated test was tautological — it asserted a locally
    re-evaluated cfg! rather than the value main reads. main now hoists
    PACKAGED / PACKAGED_PLATFORM consts and the test asserts against those.
    (Verified the complementary protection: a misspelled feature name is an
    unexpected_cfgs error under -D warnings.)
  3. A doc comment was factually wrong — the macOS guard does not prevent
    adopting the Swift Roost.app profile; on macOS Gtk resolves Roost-gtk,
    the GTK dev profile. Corrected in code and plan.

Also applied from review: Makefile added to the tests path filter (the
lane's test list lives there, so editing it must retrigger the lane).

Accepted risks

  • Split XDG_RUNTIME_DIR → two locks over one state.json. lock_path()
    keys off $XDG_RUNTIME_DIR while state_dir keys off $XDG_DATA_HOME, so
    two processes disagreeing on only the former take different flocks over the
    same state file (desktop session vs. an ssh/cron context where
    XDG_RUNTIME_DIR is unset). Pre-existing and already applied gtk-vs-gtk,
    but noted because this PR makes iced and GTK share a state namespace for the
    first time and removes the partial backstop — GApplication's D-Bus
    uniqueness is a GTK construct a packaged iced never registers. A real fix
    keys the lock to the state dir: architectural, not packaging. Flagged for
    Charlie's decision before the release.
  • --all-features would hand a dev binary the production profile. No such
    invocation exists in the repo; documented next to the feature.
  • The shed is one distro at one version. Nothing here covers an older
    Ubuntu with older Mesa.
  • build-deb.sh clears dist/ but not out/, so repeated local builds
    accumulate .debs and would trip release.yml's exactly-one-match upload
    assertion. CI always builds fresh; called out rather than fixed inside a
    packaging swap.

Impact

No dependency added to any Rust crate. No privacy or secret impact. The
package's Depends: changes substantially (GTK out, iced runtime stack in) —
that is the point, and it is measured. Package name, install paths, desktop
entry basename and icons are all unchanged, so apt-charliek's roost_*.deb
glob and users' pinned launcher items keep working.

Plan 022 § D1–D3 — the pinned design

D1 — Profile adoption: a cargo feature on roost-iced. linux-package
flips the compiled-in default via default_profile_kind(packaged, linux),
called with cfg!(feature = "linux-package") and cfg!(target_os = "linux").
Parameterized rather than a bare #[cfg] so all four cells are unit-testable
in a single build. ROOST_BUNDLE_PROFILE still wins. The linux guard keeps
it inert on macOS, where Gtk resolves the GTK dev profile and would
collide with the macOS GTK binary CLAUDE.md supports running side by side.

D2 — Identity strings derive from the resolved profile. Packaged builds
announce ai.stridelabs.Roost.gtk, matching the already-installed desktop
entry, so taskbar grouping and notification identity are correct with no
.desktop change. The .gtk suffix is deliberately kept: renaming it is a
user-visible migration (pinned launcher items keyed on StartupWMClass).

D3 — Package contents. Name stays roost; dist/roost/usr/bin/roost
is the existing contract. Depends: derived from readelf + strace +
validated by a --no-install-recommends install in a minimal environment.

Plan: ~/.claude/plans/roost/022-deb-swap.md. Sibling: #314 (W-A, merged).
W-C (#311 badge) follows separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SgxiEBQTqgNjPswKqcs12d

Summary by CodeRabbit

  • New Features

    • Linux .deb packages now use the Iced desktop interface.
    • Packaged Linux installations use consistent production application paths and desktop integration.
    • Vulkan support is recommended for improved graphics compatibility.
  • Documentation

    • Updated installation instructions with packaging requirements, build steps, and GTK/source-build guidance.
    • Added documentation describing Linux package behavior, profiles, dependencies, and runtime paths.
  • Bug Fixes

    • Improved Linux application identification for notifications, windows, taskbars, and desktop launchers.
    • Added release validation to verify packaged applications start correctly and communicate through the expected IPC paths.

charliek and others added 6 commits August 7, 2026 17:23
The Linux .deb will ship the iced UI as /usr/bin/roost. For existing users
to migrate transparently it has to land on the paths the GTK package
already owns — same socket, same state.json, same log dir — while dev
builds keep the isolated roost-iced profile for side-by-side work.

A cargo feature does it. `linux-package` flips only the compiled-in
*default* profile; BundleProfile::resolve still lets ROOST_BUNDLE_PROFILE
win, which is what makes this safe: every dev harness (ui.py:481,
screenshot/lib.sh, run-iced) pins that variable explicitly, so they stay
correct no matter how the binary was compiled. Nothing in paths.rs changes
— the packaged build resolves an existing profile kind, so the four
hand-maintained copies of the resolver (Rust, Swift, Python, Bash) are
untouched.

The decision is a pure function taking bools rather than an inline #[cfg]
so all four cells are testable in one build. The single-instance lock then
comes out right for free: lock_path() derives from the profile's socket
dir, so a packaged iced and a GTK roost contend on the same flock.

An env var baked into the .desktop Exec= was rejected — it covers only the
launcher, leaving shell-launched and roostctl-spawned instances on a
different profile. Renaming the [[bin]] to `roost` was rejected too: it
collides with roost-linux's bin name and wouldn't change profile
resolution anyway.

Verified on Linux with ROOST_TEST_PANIC (aborts after logging init, before
the lock, so nothing binds a socket): dev build creates roost-iced/,
packaged creates roost/, and both honor ROOST_BUNDLE_PROFILE overrides in
both directions.

CodeRabbit adversarial review — three real defects, all fixed here:
- The Makefile-only gate was vacuous in CI: ci.yml never invokes
  check-iced, so the feature would still have first compiled during a
  release build. The --features pair is now mirrored into the iced job.
- The feature-gated test was tautological, asserting a locally re-evaluated
  cfg! rather than the value main reads. main now hoists PACKAGED /
  PACKAGED_PLATFORM consts and the test asserts against PACKAGED, so it
  fails if the gate stops reaching the profile decision. (Verified the
  complementary protection too: a misspelled feature name is an
  unexpected_cfgs error under -D warnings.)
- The macOS-guard comment was factually wrong: Gtk on macOS resolves
  Roost-gtk, the GTK *dev* profile, not the Swift app. Corrected — the
  hazard is colliding with the macOS GTK binary, not with Roost.app.
Also noted and documented: --all-features would hand a dev binary the
production profile, so Cargo.toml warns against it.

Reviewer finding accepted as pre-existing, not fixed here: processes that
disagree on XDG_RUNTIME_DIR take different locks over one state.json.
That shape predates this change and already applied gtk-vs-gtk; it is
recorded in the plan's risks and on Charlie's checklist because this
commit removes the GApplication D-Bus backstop that partially covered it.

Test-suite note: one run of single_instance::drop_releases_so_next_acquire
_succeeds failed during a concurrent full-workspace run. It passes on a
clean tree and 3/3 with this change applied, in a crate this commit does
not touch — recorded as an observed flake rather than silently ignored.

Plan 022 C3 (workstream B).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgxiEBQTqgNjPswKqcs12d
C3 made a packaged Linux build resolve the Gtk profile, but two identity
strings stayed hardcoded to `ai.stridelabs.Roost.iced`: the winit
`application_id` (WM_CLASS on X11, app_id on Wayland) and the freedesktop
`desktop-entry` notification hint.

Left alone, the packaged build would report `ai.stridelabs.Roost.gtk` over
IPC while its window announced `ai.stridelabs.Roost.iced`. The deb installs
`ai.stridelabs.Roost.gtk.desktop` with a matching StartupWMClass, so no
entry would match the window: generic icon in the dock and app switcher, no
window grouping, StartupNotify never resolving, and a dangling notification
icon hint. That is visibly worse than the GTK package it replaces — a
migration users would notice.

Both now come from the resolved profile, so the shipped binary matches the
shipped desktop entry with no change to packaging/ at all. This is the
Linux half of #303's desktop-entry note, folded in where it was free.

The notifications half needed care. `backend::show` is handed to `spawn_on`
under `F: Fn(Payload) -> Fut`, a bound five unit tests implement via their
own `recording_backend`. Threading an app_id parameter through would have
changed the bound and all five tests, so `new` takes the id and captures it
in a closure instead: the bound, the tests, and the backend shape are
untouched.

Verified on Linux under Xvfb, reading the real window property rather than
inferring it — dev build: WM_CLASS = "ai.stridelabs.Roost.iced"; packaged
build: WM_CLASS = "ai.stridelabs.Roost.gtk". Also cross-compiled to
x86_64-unknown-linux-gnu so the Linux-only cfg branches are type- and
lint-checked from the Mac.

Gates: check-iced clean with and without the feature, e2e-iced-ci 84
passed / 3 known skips (including the walking-skeleton assertion that a dev
build still identifies as .iced).

Plan 022 C4 (workstream B).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgxiEBQTqgNjPswKqcs12d
The package now builds roost-iced with --features roost-iced/linux-package
and stages it as dist/roost. Package name, install paths, desktop-entry
basename and icons are all unchanged: apt-charliek globs roost_*.deb, and
the installed ai.stridelabs.Roost.gtk.desktop is exactly the identity the
packaged binary announces after C4. GTK leaves the package; its source
stays in-repo until the separate retirement decision.

The dependency list is the dangerous part and it is measured, not reasoned.
`readelf -d` on a release roost-iced lists three NEEDED entries — libc,
libgcc_s, libm — because winit, wgpu and ash dlopen the whole graphics and
input stack. A conventional ldd-derived Depends would therefore have been
two packages: it would have built, installed cleanly on every machine, and
passed every automated gate, then failed to launch for anyone whose system
didn't already carry the stack from the old GTK package.

So the list comes from `strace -f -e trace=openat` on a real launch, mapped
through dpkg -S: 48 packages opened, reduced to the ten the application
itself loads. The rest is Mesa's transitive closure, which its own Depends
pull and which must not be pinned here.

Vulkan is Recommends, not Depends, on measured evidence: with
/usr/share/vulkan/icd.d removed entirely the app still opens a window —
wgpu reports `Available adapters: []` and uses the software renderer
compiled in beside it. Recommends is on by default for ordinary apt
installs, so users still get GPU acceleration; only --no-install-recommends
drops to software, and that works.

Proven end to end in a pristine ubuntu:24.04 container: installed with
--no-install-recommends (so no libvulkan1, no ICD) and the app opened a
window. desktop-file-validate passes, and the installed entry's
StartupWMClass matches the WM_CLASS the packaged binary was observed to
announce.

Note for local builds: build-deb.sh clears dist/ but not out/, so repeated
local builds at different versions accumulate .debs there and would trip
release.yml's exactly-one-match upload assertion. CI always builds from a
fresh checkout, so this is a local-only wart, called out rather than fixed
inside a packaging swap.

Plan 022 C5 (workstream B).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgxiEBQTqgNjPswKqcs12d
Two changes to the linux release job, both consequences of the package now
containing the iced UI instead of GTK.

The apt set drops libgtk-4-dev and libadwaita-1-dev: build-deb.sh no longer
builds roost-linux, so they were pure install time. pkg-config and
libclang-dev stay (bindgen for roost-vt), and the iced-release CI job's
display + graphics stack joins them because the new smoke step has to
actually launch the binary.

The smoke step is the more important half. The mac job has validated its
shipped bundle since #122; the linux job has never launched what it
uploads. Nothing upstream of it can catch the two failure modes this swap
introduces — staging the wrong binary, or building without the
linux-package feature — because both produce a package that builds, lints
and installs perfectly and only misbehaves by binding the wrong IPC
namespace at runtime. So the step runs the staged dist/roost in a
throwaway XDG sandbox with ROOST_BUNDLE_PROFILE deliberately UNSET, and
asserts the socket lands under the production roost/ namespace with no
roost-iced/ directory created.

It waits on a successful `identify` round-trip rather than on the socket
file appearing. That distinction is load-bearing and was learned during
this plan's shed verification: a killed predecessor leaves a stale socket
behind, so a file-existence check can pass against a UI that is not
listening.

Plan 022 C6 (workstream B).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgxiEBQTqgNjPswKqcs12d
The iced-release lane built the featureless dev configuration, so
linux-package — the thing that makes a packaged build adopt the production
roost profile — would have compiled for the first time during an actual
release, with users on the other side of the deb upgrade.

The existing build now carries the feature. Deliberately ONE build, not
two: [profile.release] is lto = "thin" + codegen-units = 1, and a second
cold LTO link would blow the job's 45-minute budget. The packaged binary is
also the right thing to be running the e2e subset against, since it is what
ships.

That subset still exercises it on the isolated dev profile, because ui.py
pins ROOST_BUNDLE_PROFILE=iced for every iced launch and the env override
outranks the compiled-in default. That is genuinely surprising to read, so
the step now says so.

The new assertion step is what actually proves adoption: same already-built
binary, ROOST_BUNDLE_PROFILE left unset, isolated XDG dirs, and a check
that the socket lands under the production roost/ namespace with no
roost-iced/ directory. It waits on an `identify` round-trip rather than the
socket file, for the stale-socket reason recorded in C6.

Plan 022 C7 (workstream B).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgxiEBQTqgNjPswKqcs12d
Four docs went stale the moment the deb swapped binaries.

paths.md asserted the Iced profile is "always isolated" — no longer
unconditionally true. Its per-binary table row and the surrounding prose now
distinguish a packaged Linux build (resolves the production `roost`
namespace) from dev builds and every other platform (keep `roost-iced`),
and note that ROOST_BUNDLE_PROFILE still overrides either.

installation.md required GTK4 + libadwaita dev packages to build the Linux
UI. That is now only true for building roost-linux from source; the package
itself needs neither. Split so both audiences get accurate instructions,
without overclaiming — roost-linux still exists and is still built and
tested in CI.

linux/README.md was five lines describing an AppImage, tonic over a Unix
socket, a roost-core daemon, and "Phase 0 placeholder". None of that has
been true for a long time. Rewritten to describe what the directory
actually does.

The roadmap's M4 section records what shipped against the two decisions,
with the mechanism and the evidence, and states plainly that cutting the
release is a separate manual step that has not happened. It does not claim
M4 is complete. Its #309 deferred list also lost the "iced release-profile
CI" entry, which shipped as the iced-release job.

The implementing agent flagged that it could not verify the "real apt
upgrade transaction" claim from repo sources alone — correct, and worth
recording: the evidence is the container transcript
(`Unpacking roost (0.0.17~iced1) over (0.0.17~gtkbase)`) captured in the
plan's artifacts, not anything in the tree.

Plan 022 C8 (workstream B).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgxiEBQTqgNjPswKqcs12d
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 29 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f47cd729-0614-409e-8d38-db8eed795194

📥 Commits

Reviewing files that changed from the base of the PR and between 9ab8347 and d476a03.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • docs/development/iced-migration-roadmap.md
  • packaging/nfpm.yaml
📝 Walkthrough

Walkthrough

The change adds a linux-package profile for roost-iced, derives Linux identifiers from the selected profile, updates Debian packaging for Iced runtime dependencies, and validates packaged startup and production IPC paths in CI.

Changes

Linux Iced packaging

Layer / File(s) Summary
Packaged profile selection
crates/roost-iced/Cargo.toml, crates/roost-iced/src/main.rs, crates/roost-iced/src/app.rs
The linux-package feature selects the GTK profile on packaged Linux builds. Window configuration and notification setup use the resolved profile identifier. Tests cover the selection matrix (crates/roost-iced/src/main.rs:798-831).
Notification application identity
crates/roost-iced/src/notifications.rs, crates/roost-iced/tests/panic_hook_test.rs
Notification backends now receive an application identifier. The Linux backend uses it as the desktop-entry hint. Other backends ignore it. Panic-report tests use the configuration-specific application label.
Debian packaging and runtime dependencies
linux/scripts/build-deb.sh, packaging/nfpm.yaml, packaging/roost.desktop, linux/README.md, docs/getting-started/installation.md, docs/reference/paths.md, docs/development/iced-migration-roadmap.md
The Debian build stages roost-iced with roost-iced/linux-package. Package dependencies target Iced runtime libraries. Documentation describes the packaged profile, paths, build process, and validation.
Build and packaged-artifact validation
Makefile, .github/workflows/ci.yml, .github/workflows/release.yml
Local checks and CI test and lint the feature. Release jobs launch packaged binaries under Xvfb, verify IPC readiness, require the production roost namespace, and reject the roost-iced namespace.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CI
  participant PackagedBinary
  participant Xvfb
  participant roostctl
  participant XDGFilesystem
  CI->>Xvfb: start virtual display
  CI->>PackagedBinary: launch dist/roost
  PackagedBinary->>XDGFilesystem: create production roost runtime paths
  CI->>roostctl: poll identify
  roostctl->>PackagedBinary: request IPC identity
  PackagedBinary-->>roostctl: return socket information
  CI->>XDGFilesystem: verify roost namespace
  CI->>XDGFilesystem: reject roost-iced namespace
Loading

Possibly related PRs

  • charliek/roost#314: Extends the Iced release-profile CI coverage and replaces the release test binary flow with packaged Linux build validation.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: shipping the iced UI in the Debian package while using the adopted GTK production profile.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/plan-022-deb-swap

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 227-293: Update the “Smoke the packaged artifact” step to install
the generated out/*.deb in a clean Ubuntu 24.04 rootfs or container before
testing. Launch /usr/bin/roost and invoke /usr/bin/roostctl identify from that
installed environment, preserving the existing IPC namespace and failure checks
while validating the package file list and declared Depends closure.

In `@docs/development/iced-migration-roadmap.md`:
- Line 625: Update the wording in the iced migration roadmap around “Verified
end to end” to use the hyphenated compound modifier “Verified end-to-end,”
preserving the surrounding text.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6181a677-51cf-469e-a541-820d445189c8

📥 Commits

Reviewing files that changed from the base of the PR and between a04a296 and 698833d.

📒 Files selected for processing (14)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • Makefile
  • crates/roost-iced/Cargo.toml
  • crates/roost-iced/src/app.rs
  • crates/roost-iced/src/main.rs
  • crates/roost-iced/src/notifications.rs
  • docs/development/iced-migration-roadmap.md
  • docs/getting-started/installation.md
  • docs/reference/paths.md
  • linux/README.md
  • linux/scripts/build-deb.sh
  • packaging/nfpm.yaml
  • packaging/roost.desktop

Comment thread .github/workflows/release.yml
Comment thread docs/development/iced-migration-roadmap.md Outdated
charliek and others added 2 commits August 8, 2026 22:29
The new `--features linux-package` test pass added in C3 immediately earned
its keep: it turned ubuntu's iced-build-e2e red on this branch.

panic_hook_test clears ROOST_BUNDLE_PROFILE on purpose, so the child
resolves its compiled-in default — which is exactly what the packaging
feature changes. The crash report stamps profile.app_label, so under
`--features linux-package` on Linux it reads "Roost-gtk" while the test
asserted the literal "Roost-iced".

The expectation now follows the same two cfg!s main does. Integration tests
compile with the crate's feature set, so they resolve identically here.

Worth recording why the local gate missed it: on macOS the feature is inert
by design, so `make check-iced` passes both ways on this machine and only a
Linux build can fail this. Verified in a Linux VM — 2 passed with and
without the feature — as well as on macOS.

Plan 022, fix on workstream B.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgxiEBQTqgNjPswKqcs12d
…ayload

Codex review of PR #315 — four findings, all real, all fixed.

The glibc floor was wrong and would have produced exactly the failure this
plan's dependency work exists to prevent. `objdump -T` on the release binary
shows non-weak GLIBC_2.35 imports (hypot/hypotf) and GLIBC_2.34
(pthread_*, __libc_start_main); the 2.39 references are weak and don't raise
the floor. The package advertised `>= 2.31`, inherited from the GTK
package — so it would install happily on Ubuntu 20.04 or Debian 11 and then
die in the dynamic loader before a window appeared. Now `>= 2.35`, which
fails at install time with a comprehensible message instead.

A font is now a hard dependency. Codex predicted startup could terminate on
a font-less system; measured, it does not — a --no-install-recommends
install into a container with zero files under /usr/share/fonts and no
fontconfig still opened a window. But a terminal with no monospace glyphs
isn't usable even when it boots, so fonts-dejavu-core is guaranteed rather
than hoped for. (Verified the rebuilt package pulls 8 font files.)

The release smoke tested the wrong artifact. It ran ./dist/roost — the
binary build-deb.sh staged — which proves the binary works while leaving
every packaging-layer mistake (a wrong `contents:` destination, a dropped
entry, a lost exec bit) to be found by users. It now extracts the built .deb
with dpkg-deb and smokes the packaged /usr/bin/roost and /usr/bin/roostctl,
asserting both are present and executable first.

Packaging changes could also skip CI entirely: `iced-release` gated on
rust/tests/ci only, so a build-deb.sh-only PR never reached it and a
nfpm.yaml-only PR matched no filter at all. `packaging/**` joins the `linux`
filter and `iced-release` now gates on it — otherwise a packaging-only
change compiles the linux-package feature nowhere.

Finally, the roadmap's fixed guardrails still told contributors that GTK is
the shipped Linux UI and roost-iced is "absent from release artifacts",
directly contradicting the packaging note added in C8. Both corrected, with
the M4 caveat spelled out: incompleteness in a *packaged* Linux build now
does reach users, which is what the entry criteria exist to gate.

Verified: deb rebuilt in a VM, control fields checked, payload extracted and
both binaries confirmed executable, then installed --no-install-recommends
in a pristine ubuntu:24.04 container and launched — IPC ready on
/tmp/rt/roost/roost.sock, the production namespace.

Plan 022, review fixes on workstream B.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgxiEBQTqgNjPswKqcs12d
@charliek

charliek commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Codex review — findings and dispositions

Codex reviewed this PR (no Critical/High). All four findings were real and are fixed in 3699b4b.

1. glibc floor was wrong (Medium) — fixed. objdump -T on the release binary shows non-weak GLIBC_2.35 (hypot/hypotf) and GLIBC_2.34 imports; the 2.39 references are weak and don't raise the floor. The package advertised >= 2.31, inherited from the GTK package, so it would have installed cleanly on Ubuntu 20.04 / Debian 11 and then died in the dynamic loader before showing a window — the exact install-clean-then-fail shape this PR's dependency work exists to prevent. Now libc6 (>= 2.35), which fails at install with a comprehensible message.

2. Font availability (Medium) — investigated, partially confirmed, fixed. The predicted failure was startup termination on a font-less host. Measured: it does not happen — a --no-install-recommends install into a container with zero files under /usr/share/fonts and no fontconfig still opened a window. But a terminal with no monospace glyphs isn't usable even when it boots, so fonts-dejavu-core is now a hard dependency rather than a hope. Rebuilt package pulls 8 font files.

3. Packaging changes could skip CI, and the smoke tested the wrong artifact (Medium) — both fixed.

  • iced-release gated on rust/tests/ci only, so a build-deb.sh-only PR never reached it and an nfpm.yaml-only PR matched no filter at all. packaging/** now joins the linux filter and iced-release gates on it.
  • The release smoke ran ./dist/roost — the staged binary — proving the binary works while leaving every packaging-layer mistake (wrong contents: destination, dropped entry, lost exec bit) to be discovered by users. It now dpkg-deb -x's the built .deb and smokes the packaged /usr/bin/roost + /usr/bin/roostctl, asserting both exist and are executable first.

4. Roadmap guardrails contradicted the packaging (Low) — fixed. The fixed guardrails still said GTK ships to Linux users and roost-iced is "absent from release artifacts". Both corrected, with the M4 caveat made explicit: incompleteness in a packaged Linux build now does reach users.

Verification of the fixes

Deb rebuilt in a Linux VM, control fields checked, payload extracted and both binaries confirmed executable, then installed --no-install-recommends into a pristine ubuntu:24.04 container and launched — IPC ready on /tmp/rt/roost/roost.sock, the production namespace.

Separately, CI caught a real one

iced-build-e2e went red on both Linux renderers: panic_hook_test clears ROOST_BUNDLE_PROFILE so the child resolves its compiled-in default, and the crash report stamps profile.app_label — which the packaging feature moves from Roost-iced to Roost-gtk. Fixed in 9ab8347 so the expectation follows the same two cfg!s main does.

Worth noting: that test only runs in the packaged configuration because CodeRabbit's C3 review caught that the make check-iced gate was never invoked by CI. And it could only fail on Linux — on macOS the feature is inert by design, so the local gate passes both ways.

CodeRabbit review of PR #315.

Major: the smoke step extracts the .deb, which validates the payload — file
list, destinations, exec bits — but not the Depends closure. The release
runner already carries the whole graphics stack from the build step, so a
Depends: line that forgot a library would still launch there and ship.

Missing runtime dependencies are the failure users actually hit: the package
installs cleanly and then won't start. So the closure is now checked where
nothing is pre-installed — a clean ubuntu:24.04 container, installed with
--no-install-recommends so only what Depends names is present. That also
continuously proves the Recommends-vs-Depends split, since the container
gets no Vulkan loader at all and the software fallback has to carry it.

The extraction-based step stays: it is fast, needs no container, and covers
the packaging-layer mistakes the install cannot distinguish from a build
problem. The two check different things.

Minor: hyphenated "end-to-end" in the roadmap.

This is the same shape as the shed verification already recorded for this
plan, promoted into the release path so it keeps holding.

Plan 022, review fixes on workstream B.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgxiEBQTqgNjPswKqcs12d
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.

1 participant