Skip to content

Plan 015: iced native notifications (3f), PageUp/PageDown scrollback, clipboard image paste, 3g docs - #304

Merged
charliek merged 8 commits into
mainfrom
feature/plan-015-iced-3f-functional-p1
Aug 5, 2026
Merged

Plan 015: iced native notifications (3f), PageUp/PageDown scrollback, clipboard image paste, 3g docs#304
charliek merged 8 commits into
mainfrom
feature/plan-015-iced-3f-functional-p1

Conversation

@charliek

@charliek charliek commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Plan 015: iced slice 3f (native desktop notifications) plus the functional P1 remainders that 3e's visual polish won't cover. Panel-reviewed plan (GLM + CodeRabbit; Codex at its usage limit); seven gated commits; live-verified in the shed VM.

W1 — slice 3f: native desktop notifications (iced, Linux D-Bus)

crates/roost-iced/src/notifications.rs: a DesktopNotifications adapter with a per-OS backend seam — Linux ships via notify-rust 4.18 (z-with-tokio: zbus 5 rides the app's existing tokio runtime, no async-io subtree; target-scoped to Linux so mac builds gain nothing); every other target is a log-only no-op, keeping a future UNUserNotificationCenter backend a drop-in (#303). One sequential worker on the engine runtime gives GTK's per-tab replace-not-stack semantics (stored server ids), each show bounded by a 5s timeout. Click-to-focus shipped: the freedesktop default action round-trips through EngineFeed::NotificationActivated into GTK-parity reveal-and-focus (focus tab, clear badge, reveal sidebar, best-effort raise). Suppression stays engine-side (untouched, shared semantics); the adapter never runs inside engine locks — the inventory's pinned constraint, satisfied structurally. Listener lifecycle is bounded: abort-on-refire/retire keeps at most one live action listener per tab (panel finding).

W2 — PageUp/PageDown local scrollback (both Rust UIs, shared policy)

roost-vt::TerminalScroll::route_page extends the shared wheel policy with page-wise navigation under the identical mode precedence (mouse tracking → forward, alternate screen → forward with the real \x1b[5~/\x1b[6~ bytes, else one full-viewport local page; wheel momentum discarded; authoritative bottom re-read incl. the Err conservatism, shared with route() by extraction). Both UIs intercept bare page keys only, after keybind routing and before the pre-key snap — the snap bypass is conditioned on the policy's LocalViewport verdict, never the key; local pages preserve the selection; modified combos and forwarded bytes are byte-identical to before (fixture-pinned). GTK masks lock modifiers and accepts keypad variants. The Swift Mac app is deliberately unchanged (it has no such navigation; recorded in the inventory). The Linux real-input gate gains a physical PageUp/PageDown segment in the already-enumerated iced_clipboard_check.py — no new e2e module, no new IPC op.

W3 — clipboard image materialization on paste (iced)

An empty System-clipboard text read on paste now probes for image data via arboard 3.6 (image-data + wayland-data-control with verified X11 fallback) on the blocking pool, encodes PNG through a new shared png_encode helper (deduplicated with the screenshot module), and pastes the temp-file path through the normal bracketed-paste wrap. GTK-spec parity: 40·1024·1024 pixel cap, 10 MiB cap on the encoded output before any file exists, roost-image-{nanos}-{16hex}.png, create_new + 0o600, no cleanup, no escaping (generated charset unit-pinned as the #282 defense-in-depth). PRIMARY paste never probes. Documented divergences (inventory row): pixel cap runs post-decode (arboard decodes internally — weaker than GTK's pre-decode gate), uri-list-only file copies remain GTK-only, macOS is compiled-but-unverified this pass.

W4 — upstream-blocked gaps documented (3g discipline)

Roadmap 3f marked complete; 3g expanded into the four upstream iced/winit gaps (no drop coordinates, no text/URI-list drop events, no Wayland DnD, clipboard seat serial) with the pinned track-and-document disposition — no workarounds, no upstream engagement. New tracking issues: #302 (drop/DnD/Wayland gaps), #303 (notifications follow-ups). Parity-inventory rows updated to post-merge truth; CLAUDE.md library table gains notify-rust + arboard rows.

Verification

  • Every commit gated: make check-iced (incl. an extended roost-engine boundary grep now also forbidding notify-rust|zbus|arboard), cargo test --workspace on manifest-touching commits, full -D warnings clippy for the roost-linux commit. 240 roost-iced tests, 147 roost-linux tests, 24 roost-vt scroll fixtures at final state.
  • Shed (real Linux, X11): full iced_clipboard_check.py real-input gate PASS including the new page-key segment; notifications verified against a mock org.freedesktop.Notifications server — payload/hints exact, live replace (replaces: <prev id>), and the full click-to-focus round trip (ActionInvoked on an inactive tab's banner switched focus and cleared only that tab's badge); image paste end-to-end (xclip image/png → pasted path → mode-600 PNG round-trips).
  • Honest limits: engine focus-suppression gate not exercisable headless (unit-tested engine-side); no real notification daemon rendering (mock only); Wayland/macOS image-paste read paths not live-verified; W3 has no committed e2e (needs xclip in CI images — recorded as future work).

Dependencies / risk

Two new deps, both iced-side only and boundary-grep-enforced: notify-rust (Linux-target-scoped; +42 lock entries, 21 compiled on Linux, 0 on mac) and arboard (+18 lock entries, several target-gated; brings a transitive second png codec — recorded wart, we encode via the existing png 0.18). Accepted risks dispositioned in review: wedged-daemon queue latency (unbounded-channel pattern shared with EngineFeed), success-then-timeout id desync (logged, pathological-bus only), banner-click-vs-replace race clears the tab's pending flag (parity-by-design — focusing a tab clears its badge in every UI).

Plan 015 (panel-reviewed, full text)

See ~/.claude/plans/roost/015-iced-3f-and-functional-p1.md (local convention). Key pinned decisions: per-OS notification seam with no half-shipped macOS backend; bare-key page navigation with wheel-identical precedence (known cost: primary-screen apps lose forwarded bare \x1b[5~; a page_keys knob can be added backward-compatibly later); System-only image probe mirroring the GTK/Swift spec including its 0600 mode; docs-only treatment of upstream DnD gaps. Panel corrections adopted: listener abort-on-refire, snap-bypass-on-route-verdict wording, encoded-output cap (real GTK parity), selection preservation pin, GTK lock-modifier masking, engine-runtime pin for the worker, boundary-grep extension, objective fallback trigger for click-to-focus (not needed — it shipped).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WLKWsLV45DAk61xG6Utj3e

Summary by CodeRabbit

  • New Features
    • Added Linux desktop notifications with per-tab replacement and click-to-focus behavior.
    • Added clipboard image pasting with PNG conversion and temporary-file handling.
    • Added Page Up/Page Down terminal scrollback navigation, including alternate-screen forwarding.
  • Bug Fixes
    • Improved notification cleanup when tabs are cleared or closed.
    • Preserved clipboard targets during asynchronous paste operations.
  • Documentation
    • Updated migration and feature-parity documentation for notifications, scrolling, and image pasting.

charliek and others added 7 commits August 5, 2026 09:20
route_page(direction, viewport_rows) extends TerminalScroll with the
same mode precedence as the wheel (mouse tracking -> Forward, alternate
screen -> Forward with the real key encoded by the caller, else local).
A local page moves exactly one full viewport, discards accumulated
wheel fractions, and re-reads the authoritative scrollbar bottom state
through the same path as the wheel: the shared tail is extracted into
move_local_viewport so the Err conservatism lives in one place.

Plan 015 C1. Review: codex at usage limit, cursor returned no output;
careful self-review (sign convention, route() extraction parity,
momentum reset, clamp semantics) found no issues. Gate: fmt, cargo
test -p roost-vt --features ffi (24 passed), cargo test --workspace,
workspace + roost-linux + roost-vt(ffi) clippy -D warnings all clean.

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

Bare (zero-modifier) page keys on a live terminal route through
roost_vt::route_page after keybind dispatch and before the pre-key
snap: a LocalViewport verdict pages one full viewport with no PTY
bytes and no snap, Forward (mouse tracking, alternate screen) falls
through to the unchanged snap+encode path so today's \x1b[5~ family
bytes keep flowing, modified combos never enter the branch.

TerminalTab::handle_page refreshes its snapshot on the local route;
selection state is untouched (fixtures pin selection survival and
exact page-sized offsets, zero captured PTY bytes locally, exact
forward bytes on alt-screen/tracking/shift). The Linux real-input
check gains a physical PageUp/PageDown segment (local dump assertion +
alt-screen forward capture) in the already-enumerated
iced_clipboard_check.py.

Plan 015 C2. Cursor review: no findings. Gate: make check-iced green
(224 tests + clippy -D warnings + boundary greps), py_compile clean.

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

GTK twin of the iced adapter (77d776c): a bare page key (Page_Up/
Page_Down and keypad variants; Shift/Ctrl/Alt/Super/Meta disqualify,
lock modifiers deliberately do not — GDK4 has no NumLock bit) routes
through roost_vt::route_page before the pre-key snap and the #99
selection clear. LocalViewport consumes the key with a redraw and
preserves the selection; Forward falls through byte-identical to
today's snap/clear/encode path. handle_page_key is the testable seam;
fixtures pin classification (incl. lock masking), full-page motion,
and alt-screen/mouse-tracking forwarding at the policy level (a
realized GTK widget is impractical headless — same coverage style as
roost-vt's own suite).

Plan 015 C3. Cursor review: no findings. Gate: fmt, cargo test -p
roost-linux (147 tests, 0 failures), clippy -p roost-linux
--all-targets -D warnings clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLKWsLV45DAk61xG6Utj3e
DesktopNotifications drains an unbounded fire/retire channel on the
engine runtime with one sequential worker: per-tab server ids give
GTK's replace-not-stack semantics, each show is bounded by a 5s
timeout that warns and keeps draining, and the per-OS backend seam
ships Linux via notify-rust 4.18 (z-with-tokio: zbus 5 rides the
existing tokio runtime, no async-io subtree) while every other target
logs and returns None so a future UNUserNotificationCenter backend is
a drop-in. Payload mirrors GTK: title verbatim, body omitted when
empty, appname Roost, desktop-entry hint ai.stridelabs.Roost.iced.
The NotificationFired arm now feeds the adapter beside the inbox
upsert; TabClosed retires the slot. notify-rust is target-scoped to
Linux (mac tree gains nothing) and the check-iced roost-engine grep
now also forbids notify-rust/zbus/arboard.

Plan 015 C4. Cursor adversarial review: retire-on-clear replace break
FIXED (clear now keeps the server id — GTK's constant per-tab id is
the parity bar); success-then-timeout id desync ACCEPTED as logged
pathological-bus behavior (comment); wedged-daemon queue amplification
ACCEPTED (same unbounded pattern as EngineFeed, plan risk note).
Simplify pass: dead derives trimmed. Lock: +42 crates (21 compile on
Linux, rest are notify-rust's non-Linux backends, never built).

Gate: make check-iced (228 tests, extended grep), cargo test
--workspace, workspace clippy -D warnings — all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLKWsLV45DAk61xG6Utj3e
The Linux backend adds the freedesktop default action; after each show
the worker spawns one listener awaiting the click (opaque boxed future,
so notify-rust types stay behind the cfg seam) and stores its
AbortHandle in the tab slot — refire and retire abort the previous
listener, so a tab never holds more than one, even against servers
that emit neither ActionInvoked nor Closed. A click lands on the
engine feed as NotificationActivated and drains in arrival order; the
handler mirrors GTK reveal_and_focus_tab: core focus_tab (guards a
closed tab), clear has_notification, reveal the sidebar, best-effort
window raise via UiTask::Focus.

Plan 015 C5. Cursor adversarial review: one finding — a click racing a
same-tab replace clears the newer notification's pending flag —
SKIPPED as parity-by-design: focusing a tab clears its pending state
in every UI (sidebar clicks included), GTK's focus-tab action has the
same no-generation semantics, and the user is on the tab when it
happens. Gate: make check-iced (233 tests + boundary greps), fmt,
Linux cross-check of the backend, no new dependencies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLKWsLV45DAk61xG6Utj3e
An empty System-clipboard text read on the paste path now probes the
clipboard for image data (arboard 3.6, image-data +
wayland-data-control with X11 fallback) on the blocking pool, encodes
RGBA to PNG through the new shared png_encode helper (screenshot keeps
Compression::Fast; paste uses default), and pastes the temp-file path
through the tab's normal bracketed-paste wrap. Parity with the GTK
spec: 40*1024*1024 pixel cap pre-encode, 10 MiB cap on the encoded
output before any file exists, roost-image-{nanos}-{16hex}.png in the
OS temp dir with create_new + 0o600, no cleanup, no shell escaping
(the generated charset is the #282 defense-in-depth pin, unit-tested).
PRIMARY-selection paste never probes; missing tab or any failure is a
logged no-op; the clipboard queue resumes ahead of the probe
(start_next().then(probe)).

Known, plan-documented divergences: the pixel cap runs post-decode
(arboard decodes internally — weaker than GTK's size-prepared gate),
uri-list-only file copies stay GTK-only, and macOS is compiled but not
live-verified this pass.

Plan 015 C6. Cursor review: no findings. Simplify: png encoder
deduplicated into png_encode.rs. Lock: +18 crates, several
target-gated. Gate: make check-iced (240 tests), cargo test
--workspace, workspace clippy -D warnings — green.

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

Roadmap: 3f marked complete (Linux D-Bus adapter, per-OS seam, replace
semantics, click-to-focus; macOS + .desktop follow-ups in #303); 3g
expanded into the four upstream-blocked gaps with the track-and-
document disposition (#302); slice-order paragraph refreshed. Parity
inventory: scrollback rows closed (wheel + page keys through the
shared policy, Swift divergence recorded), drop rows re-scoped to the
upstream list with the shipped clipboard-image adapter and its three
documented divergences, notifications row implemented-on-Linux.
CLAUDE.md library table gains notify-rust and arboard rows.

Plan 015 C7. Docs-only; self-reviewed.

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

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a42e16cf-f4b1-4177-849d-f5964e1d013a

📥 Commits

Reviewing files that changed from the base of the PR and between 4d84505 and a35343e.

📒 Files selected for processing (2)
  • crates/roost-iced/Cargo.toml
  • docs/development/iced-parity-inventory.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/development/iced-parity-inventory.md
  • crates/roost-iced/Cargo.toml

📝 Walkthrough

Walkthrough

The PR adds Linux desktop notifications, clipboard image materialization for Iced paste, and shared Page Up/Page Down terminal scrolling. It updates dependency boundaries, asynchronous task handling, tests, physical-input coverage, and Iced migration documentation.

Changes

Desktop notifications

Layer / File(s) Summary
Notification adapter and platform wiring
crates/roost-iced/Cargo.toml, crates/roost-iced/src/notifications.rs, Makefile, CLAUDE.md
Adds worker-backed notifications with Linux D-Bus support, per-tab replacement IDs, activation listeners, timeouts, retirement, and non-Linux fallback behavior.
Notification state and activation handling
crates/roost-iced/src/app.rs, crates/roost-iced/src/app/servicing.rs, crates/roost-iced/src/engine_feed.rs
Stores notification state, records inbox entries, dispatches notifications, handles activation events, focuses tabs, clears notifications, and raises windows when possible.
Notification migration records
docs/development/iced-migration-roadmap.md, docs/development/iced-parity-inventory.md
Records Linux notification completion and deferred macOS and platform follow-ups.

Clipboard image paste

Layer / File(s) Summary
PNG encoding and secure image materialization
crates/roost-iced/src/paste_image.rs, crates/roost-iced/src/png_encode.rs, crates/roost-iced/src/screenshot.rs, crates/roost-iced/Cargo.toml
Reads clipboard RGBA data, validates limits and payloads, encodes PNG data, and writes private temporary files. Screenshot encoding uses the shared helper.
Asynchronous paste integration
crates/roost-iced/src/app.rs, crates/roost-iced/src/app/interactions.rs, crates/roost-iced/src/main.rs, CLAUDE.md
Preserves clipboard targets, probes empty system pastes in a blocking task, resumes queued work, and delivers materialized image paths to the requesting tab.

Terminal page scrolling

Layer / File(s) Summary
Shared page routing contract
crates/roost-vt/src/scroll.rs, crates/roost-vt/src/lib.rs
Adds PageDirection, PageRoute, and TerminalScroll::route_page for local viewport paging and forwarded page keys.
Iced page-key handling
crates/roost-iced/src/input.rs, crates/roost-iced/src/app.rs, crates/roost-iced/src/app/terminal_tab.rs, crates/roost-iced/src/app/interactions.rs
Classifies bare page keys, performs local scrolling, preserves selection, and forwards modified or policy-controlled keys.
Linux terminal input integration
crates/roost-linux/src/terminal_view.rs
Handles regular and keypad page keys while preserving forwarding on alternate screens and mouse tracking.
Physical-input regression coverage
tools/input/linux/iced_clipboard_check.py, docs/development/iced-parity-inventory.md
Adds physical Page Up/Page Down coverage and records terminal scrollback completion.

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

Possibly related issues

Possibly related PRs

  • charliek/roost#149 — Adds related clipboard image materialization, PNG validation, and temporary-file handling.
  • charliek/roost#296 — Shares the Iced App, EngineFeed, and asynchronous UI task architecture.
🚥 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 summarizes the pull request's main changes: native notifications, PageUp/PageDown scrollback, clipboard image paste, and documentation.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/plan-015-iced-3f-functional-p1

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: 6

🧹 Nitpick comments (1)
crates/roost-iced/src/notifications.rs (1)

34-100: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove declaration comments that do not give non-obvious rationale.

crates/roost-iced/src/notifications.rs:34-100 includes comments that restate private enum variants, fields, and type aliases. Keep the comments that explain ordering, replacement, and cancellation invariants.

As per coding guidelines, “Default to no comments; add comments only for non-obvious rationale such as hidden constraints, workarounds, or tricky invariants.”

🤖 Prompt for 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.

In `@crates/roost-iced/src/notifications.rs` around lines 34 - 100, Remove
comments in Request, TabSlot, Activation, Shown, and Payload that merely restate
their declarations or field meanings, while preserving comments documenting
non-obvious replacement, ordering, cancellation, backend, or lifecycle
invariants. Keep the implementation and derives unchanged.

Source: Coding guidelines

🤖 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 `@crates/roost-iced/Cargo.toml`:
- Around line 38-45: Update the comment above the arboard dependency to remove
the inaccurate claim that arboard’s image dependency stays out of the picture,
while preserving the remaining explanations of image-data and
wayland-data-control.

In `@crates/roost-iced/src/app/interactions.rs`:
- Around line 1746-1753: Replace the direct TerminalTab::paste call in
deliver_paste_image with the shared workspace paste-image operation, passing the
target tab and image path. Update Iced and other frontend adapters to invoke
this operation and handle its result, preserving the closed-tab discard behavior
while ensuring terminal mutation and frontend behavior flow through the
workspace operation set.

In `@crates/roost-iced/src/main.rs`:
- Around line 479-499: Bound concurrent clipboard-image probes in the
PasteImageProbe handling of the update flow, coordinating with the queue-resume
behavior in interactions.rs so new requests cannot start unbounded
spawn_blocking tasks. Add or reuse a bounded concurrency mechanism or coalesce
pending probes, while preserving the existing PasteImageMaterialized success and
error message handling.

In `@crates/roost-iced/src/notifications.rs`:
- Around line 148-152: Update the Request::Retire handling to close the
server-side notification through the stored NotificationHandle before aborting
its listener. Use notify-rust 4.18.0’s close_async API, ensuring the close
operation is awaited or otherwise completed before slot.abort_listener() and
preserving the existing slot removal flow.

In `@crates/roost-iced/src/paste_image.rs`:
- Around line 99-110: Add deletion ownership for paths returned by
write_temp_png, retaining each materialized PNG for the paste lifetime and
removing it when its tab closes or the application exits. Thread cleanup
ownership alongside the PathBuf/String flow through main.rs and the terminal
interaction handling, ensuring both tab-close and shutdown paths remove all
temporary PNG files while preserving existing paste behavior.

In `@docs/development/iced-parity-inventory.md`:
- Around line 142-148: Update the later clipboard-image entry around the
adapter-work status to remove the stale deferral and reflect that clipboard
image materialization is shipped, matching the status already documented in the
File/image drops entry. Keep the inventory’s remaining adapter limitations and
documented divergences unchanged.

---

Nitpick comments:
In `@crates/roost-iced/src/notifications.rs`:
- Around line 34-100: Remove comments in Request, TabSlot, Activation, Shown,
and Payload that merely restate their declarations or field meanings, while
preserving comments documenting non-obvious replacement, ordering, cancellation,
backend, or lifecycle invariants. Keep the implementation and derives unchanged.
🪄 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: 54c49534-8daa-4f7d-ad35-d1132099711e

📥 Commits

Reviewing files that changed from the base of the PR and between fe45f41 and 4d84505.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • CLAUDE.md
  • Makefile
  • crates/roost-iced/Cargo.toml
  • crates/roost-iced/src/app.rs
  • crates/roost-iced/src/app/interactions.rs
  • crates/roost-iced/src/app/servicing.rs
  • crates/roost-iced/src/app/terminal_tab.rs
  • crates/roost-iced/src/engine_feed.rs
  • crates/roost-iced/src/input.rs
  • crates/roost-iced/src/main.rs
  • crates/roost-iced/src/notifications.rs
  • crates/roost-iced/src/paste_image.rs
  • crates/roost-iced/src/png_encode.rs
  • crates/roost-iced/src/screenshot.rs
  • crates/roost-linux/src/terminal_view.rs
  • crates/roost-vt/src/lib.rs
  • crates/roost-vt/src/scroll.rs
  • docs/development/iced-migration-roadmap.md
  • docs/development/iced-parity-inventory.md
  • tools/input/linux/iced_clipboard_check.py

Comment thread crates/roost-iced/Cargo.toml
Comment thread crates/roost-iced/src/app/interactions.rs
Comment thread crates/roost-iced/src/main.rs
Comment on lines +148 to +152
Request::Retire { tab_id } => {
if let Some(mut slot) = slots.remove(&tab_id) {
slot.abort_listener();
}
continue;

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline crates/roost-iced/src/notifications.rs --items all
rg -n -C 5 'Request::Retire|slots\.remove|abort_listener|NotificationHandle|close_async|wait_for_action_async' \
  crates/roost-iced/src/notifications.rs
rg -n -A 4 -B 2 '^name = "notify-rust"$' Cargo.lock

Repository: charliek/roost

Length of output: 4542


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- notification worker and backend ---'
sed -n '40,95p;140,238p;239,318p' crates/roost-iced/src/notifications.rs

printf '%s\n' '--- dependency declarations and all notify-rust references ---'
rg -n -C 3 'notify-rust|NotificationHandle|close_async|wait_for_action_async' \
  Cargo.toml Cargo.lock crates
printf '%s\n' '--- retirement call site ---'
sed -n '445,475p' crates/roost-iced/src/app/servicing.rs

printf '%s\n' '--- notification tests ---'
sed -n '319,520p' crates/roost-iced/src/notifications.rs

Repository: charliek/roost

Length of output: 20320


🌐 Web query:

notify-rust 4.18.0 NotificationHandle close_async wait_for_action_async API

💡 Result:

In notify-rust 4.18.0, the NotificationHandle struct provides async methods to interact with notifications, specifically on platforms utilizing the D-Bus protocol (XDG standards) [1][2]. These features are gated behind the zbus feature flag, which is enabled by default in many contexts [1][2][3]. NotificationHandle::wait_for_action_async This method returns a future that waits for the user to act on a notification and subsequently executes an invocation closure [1][2]. Method signature: pub async fn wait_for_action_async(&self, invocation_closure: F) where F: FnOnce(&NotificationResponse) NotificationHandle::close_async This is the asynchronous equivalent of the close method, used to programmatically close a notification [1][2]. Method signature: pub async fn close_async(&self) Important Considerations: Platform Limitation: These async methods are primarily available for the D-Bus (XDG) backend [1][2]. Specifically, close_async will panic if called with a D-Bus backend that does not support the operation [1][2]. Feature Gating: Ensure your Cargo.toml has the necessary features enabled (typically zbus) to access these async APIs, as they are not available on all platforms (e.g., they are generally not implemented for macOS or Windows) [1][2][4]. Usage: These methods are designed to be used in asynchronous Rust contexts, allowing you to await actions or closing events without blocking the thread [1]. Typical usage involves concurrently handling the notification's lifecycle, often by using a library like futures to zip or join multiple futures [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import urllib.request
urls = [
    "https://docs.rs/notify-rust/4.18.0/src/notify_rust/notification_handle.rs.html",
    "https://docs.rs/notify-rust/4.11.3/src/notify_rust/notification_handle.rs.html",
]
for url in urls:
    print(f"--- {url} ---")
    try:
        text = urllib.request.urlopen(url, timeout=10).read().decode()
        for needle in ("struct NotificationHandle", "close_async", "wait_for_action_async", "impl Clone"):
            pos = text.find(needle)
            print(f"{needle}: {'present' if pos >= 0 else 'absent'}")
            if pos >= 0:
                print(text[max(0, pos-300):pos+900].replace("&gt;", ">").replace("&lt;", "<"))
    except Exception as exc:
        print(type(exc).__name__, exc)
PY

printf '%s\n' '--- manifest and lock resolution ---'
rg -n -C 4 'notify-rust' Cargo.toml Cargo.lock

Repository: charliek/roost

Length of output: 1291


Close the displayed notification when the tab retires.

crates/roost-iced/src/notifications.rs:148-152 aborts the listener, but it does not close the server-side notification. Retain a close-capable NotificationHandle or send a close command to the listener before aborting it. Use the locked notify-rust 4.18.0 API, which provides close_async.

🤖 Prompt for 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.

In `@crates/roost-iced/src/notifications.rs` around lines 148 - 152, Update the
Request::Retire handling to close the server-side notification through the
stored NotificationHandle before aborting its listener. Use notify-rust 4.18.0’s
close_async API, ensuring the close operation is awaited or otherwise completed
before slot.abort_listener() and preserving the existing slot removal flow.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Skipping as parity-by-design: neither shipped UI withdraws a delivered banner when its tab closes (GTK never calls withdraw_notification; Swift leaves delivered notifications in Notification Center), so closing on retire would exceed the parity bar and fork the shared semantics. A click on a stale banner is guarded — the activation handler drops activations for tabs that no longer exist (unit-pinned). If the product later wants withdraw-on-close it belongs to all three UIs at once; noted on #303.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- notification retirement ---'
sed -n '140,160p' crates/roost-iced/src/notifications.rs

printf '%s\n' '--- notification activation handling and tests ---'
rg -n -C 5 'NotificationActivated|focus_tab_and_clear|focus_tab_in_core|missing.*tab|closed.*tab|no.*tab' \
  crates/roost-iced/src/app/servicing.rs crates/roost-iced/src/notifications.rs

Length of output: 22566


@charliek, verification confirms the rationale.

crates/roost-iced/src/notifications.rs:148 removes the retired tab’s slot and aborts its activation listener. It intentionally does not close the delivered notification.

crates/roost-iced/src/app/servicing.rs:180-184 logs and ignores an activation when focus_tab_in_core cannot find the tab. crates/roost-iced/src/app/servicing.rs:465-467 retires the adapter state when a tab closes. The listener-retirement test is at crates/roost-iced/src/notifications.rs:528-533.

The requested close_async behavior would break the stated GTK and Swift parity. The finding remains withdrawn.

You are interacting with an AI system.

Comment thread crates/roost-iced/src/paste_image.rs
Comment thread docs/development/iced-parity-inventory.md
CodeRabbit PR #304: the arboard WHY comment claimed its image dep
stays out of the picture — it decodes; only the PNG encode stays on
the workspace png crate. The inventory's freestanding drops note still
deferred clipboard-image materialization after the row above it
recorded it shipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLKWsLV45DAk61xG6Utj3e
@charliek
charliek merged commit 44f0631 into main Aug 5, 2026
18 checks passed
@charliek
charliek deleted the feature/plan-015-iced-3f-functional-p1 branch August 5, 2026 17:00
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