Shell theming + launcher (Daylight), clippy 1.97, deepseek audit #4

Merged
Breadway merged 27 commits from feature/theme-spotlight into main 2026-08-31 15:41:45 +08:00
Owner

Rebased onto origin/main. 27 commits: 23 shell-theme/launcher, 1 clippy-1.97 cleanup, 3 from fix/deepseek-audit.

Shell theming + launcher (the theme-spotlight work)

bread_theme::shell manifest system (phases 1–6c), Hyprland layer-rule generation, bread-launcher (headless core + GTK4 results widget, query modes + result sections), bread-capture capsule views, and three new builtin themes culminating in Daylight (bottom-anchored, segmented, light). surfaces.*.anchor is now hard-validated; spring_to clamps to [from,to]; hot-reload re-arms on theme switch.

Sub-branches feature/{compositor-rules,launcher-core,shell-theme-manifest,theme-glass-workbench} are all folded in — prune after merge.

Backward compatibility

bread-theme's public API delta is purely additive (pub mod anim, pub mod shell, layerrules_* exports) — no removals or renames. v0.7.4 consumers (bos-settings, breadpaper, breadpad, breadsearch) keep compiling. The one behavioural tightening: a theme.toml with a missing/unknown surfaces.*.anchor is now a hard error instead of a silent default — the builtins all pass, and user theme.tomls are rare. No DEPRECATIONS.md needed (nothing deprecated).

Clippy 1.97 cleanup (a86c312)

origin/main itself fails cargo clippy --workspace -- -D warnings under clippy 0.1.97 (toolchain drift). Mechanical, behaviour-preserving: type AppCssBuilder alias, sort_by_key, io::Error::other, explicit .truncate(false) on the singleton lock file, elided lifetimes. (bread-ecosystem CI does not run clippy.)

fix/deepseek-audit (3 commits)

  1. bread-polkit — the agent prompt's editable username field was passed straight to PAM, so a request scoped to specific accounts could be redirected to any local user. Now only unix-user identities polkit actually offered are accepted. Also: exit(1) when the single-instance lock can't be taken (a second agent would serve_at the same object path).
  2. bakery — a failed data-archive download left an empty NamedTempFile that reached tar tvf and aborted the whole install with a misleading error. Gate extraction on non-empty.
  3. bread-theme / bread-utils — XDG_CONFIG_HOME/XDG_RUNTIME_DIR honoured only when absolute; atomic_write pid-suffixes its temp file (concurrent generate-output race); hypr::socket_path derives /run/user/<uid> from the real uid, not a hardcoded 1000; bread-theme --help → stdout.

Checks

cargo build --workspace clean · cargo test --workspace all pass (~323) · cargo clippy --workspace --all-targets -- -D warnings clean.

After merge

Tag v0.7.5 on main → then breadbox (feature/launcher-core + fold feature/launcher-theming) → then breadbar (feature/theme-spotlight + fix/osd-pactl-watcher-cleanup), both bumping ecosystem pins to v0.7.5.

Rebased onto `origin/main`. 27 commits: 23 shell-theme/launcher, 1 clippy-1.97 cleanup, 3 from `fix/deepseek-audit`. ## Shell theming + launcher (the theme-spotlight work) `bread_theme::shell` manifest system (phases 1–6c), Hyprland layer-rule generation, `bread-launcher` (headless core + GTK4 results widget, query modes + result sections), `bread-capture` capsule views, and three new builtin themes culminating in **Daylight** (bottom-anchored, segmented, light). `surfaces.*.anchor` is now hard-validated; `spring_to` clamps to `[from,to]`; hot-reload re-arms on theme switch. Sub-branches `feature/{compositor-rules,launcher-core,shell-theme-manifest,theme-glass-workbench}` are all folded in — prune after merge. ## Backward compatibility `bread-theme`'s public API delta is **purely additive** (`pub mod anim`, `pub mod shell`, `layerrules_*` exports) — no removals or renames. v0.7.4 consumers (bos-settings, breadpaper, breadpad, breadsearch) keep compiling. The one behavioural tightening: a `theme.toml` with a missing/unknown `surfaces.*.anchor` is now a hard error instead of a silent default — the builtins all pass, and user theme.tomls are rare. No `DEPRECATIONS.md` needed (nothing deprecated). ## Clippy 1.97 cleanup (`a86c312`) `origin/main` itself fails `cargo clippy --workspace -- -D warnings` under clippy 0.1.97 (toolchain drift). Mechanical, behaviour-preserving: `type AppCssBuilder` alias, `sort_by_key`, `io::Error::other`, explicit `.truncate(false)` on the singleton lock file, elided lifetimes. (bread-ecosystem CI does not run clippy.) ## fix/deepseek-audit (3 commits) 1. **bread-polkit** — the agent prompt's editable username field was passed straight to PAM, so a request scoped to specific accounts could be redirected to any local user. Now only `unix-user` identities polkit actually offered are accepted. Also: exit(1) when the single-instance lock can't be taken (a second agent would `serve_at` the same object path). 2. **bakery** — a failed data-archive download left an empty `NamedTempFile` that reached `tar tvf` and aborted the whole install with a misleading error. Gate extraction on non-empty. 3. **bread-theme / bread-utils** — `XDG_CONFIG_HOME`/`XDG_RUNTIME_DIR` honoured only when absolute; `atomic_write` pid-suffixes its temp file (concurrent `generate-output` race); `hypr::socket_path` derives `/run/user/<uid>` from the real uid, not a hardcoded 1000; `bread-theme --help` → stdout. ## Checks `cargo build --workspace` clean · `cargo test --workspace` all pass (~323) · `cargo clippy --workspace --all-targets -- -D warnings` clean. ## After merge Tag **v0.7.5** on `main` → then breadbox (`feature/launcher-core` + fold `feature/launcher-theming`) → then breadbar (`feature/theme-spotlight` + `fix/osd-pactl-watcher-cleanup`), both bumping ecosystem pins to v0.7.5.
Implements the shell theme manifest layer from THEME_SYSTEM_PLAN.md
§4-5: ShellTheme/WindowSpec/Slots/Tokens/LayerRule types, TOML
discovery (user -> system -> compiled-in builtin), one level of
`extends` deep-merge, deny_unknown_fields validation naming the
offending key, slot module-name validation, and css() token
substitution with an extra.css overlay. load() never fails, falling
back to the compiled-in builtin and logging once.

Ships exactly one builtin manifest, liquid-motion, describing
breadbar/breadbox as they exist today (not the design-doc demo, which
disagrees with the code on bar side margin, launcher geometry, and the
easing curves). Compositor rules and surface specs are keyed by
layer-shell namespace and cover all five breadbar namespaces plus
breadbox/breadbar-panel/breadbar-dismiss.

watch() is gated behind the existing `gtk` feature (gio::FileMonitor
is a gtk4 dependency); the rest of the module is gtk-free so bread and
breadcrumbs can validate a theme without linking GTK. No consumer
changes — breadbar/breadbox still use their own hardcoded values.
Reproduces breadbar's now-removed fixed Lua-widget interleave
(right-of-workspaces, left/right-of-clock, left-of-stats) as explicit
widget: slot entries, so the builtin theme still renders pixel-identical
to today's bar under breadbar's new theme-driven widget placement.
`tray` deliberately has no slot entry anywhere — it stays in the
control-panel popover regardless of [bar.slots].

Updates the one test asserting the builtin's slot contents.
Adds `bread-theme layerrules`, which writes the active shell theme's
[compositor] table to ~/.config/hypr/layerrules.json (atomic write). This
lets ~/.config/hypr/scripts/ui/rules.lua read theme-driven blur/transparency/
animation for the breadbar/breadbox layer-shell namespaces instead of having
them hardcoded, following THEME_SYSTEM_PLAN.md §9. rules.lua keeps its
previous hardcoded rules as a pcall-guarded fallback for when the JSON is
missing or malformed (lives outside this repo, so not part of this commit).

Also fixes a pre-existing test race: shell::tests and the new
layerrules::tests both mutate XDG_CONFIG_HOME in parallel `cargo test`
threads but previously used separate, unrelated locks (or none), so they
could observe each other's env var changes mid-test. Both now share
bread_theme::test_support::XDG_CONFIG_HOME_LOCK.
radius (20 -> 8) and icon_px (36 -> 32) were demo-derived aspirations, not
what breadbox implements today (.launcher-bg's actual border-radius and
make_icon's actual set_pixel_size). row_anim/rule/footer/sections/modes are
kept but marked declared-but-not-yet-consumed: breadbox implements none of
row animation, a rule/divider, a footer, sections, or query modes today, so
the manifest should say so rather than imply they're live. Adds a builtin
launcher test mirroring the existing window/tokens fidelity tests.
Phase 5 of the shell theme system (THEME_SYSTEM_PLAN.md §11): a second
builtin, demo 02's flush edge-to-edge bar with pill workspaces, a plain
date+time clock, and cpu/ram chips instead of the media widget.

- bread-theme/assets/shell/glass-workbench/: theme.toml + CSS template,
  faithful to bos-ui-demos/02-glass-workbench.html. Accent maps to the
  `green` palette token (flat, not a gradient) rather than a hex literal,
  so pywal theming still works.
- builtin.rs: generalized from a single hardcoded liquid-motion constant
  pair to a small BuiltinTheme registry (builtin::ALL / builtin::find),
  so mod.rs's discovery/list()/resolve_builtin no longer special-case one
  id. liquid-motion stays the pinned fallback in resolve_builtin().
- manifest.rs: KNOWN_MODULES gains "cpu"/"ram".
- types.rs: new Tokens::bar_border() ("full" default vs "bottom") so a
  flush bar can ask for a single hairline instead of an island's full
  border.
- Tests: builtin loads, appears in list() alongside liquid-motion, and its
  window spec is the flush/edge shape (36px, zero margin, radius 0).

cargo test -p bread-theme --lib: 63 passing (59 prior + 4 new).
Extracts the app-launcher substance (desktop-entry parsing, fuzzy
matching/ranking, launch history, launching) out of breadbox into a
reusable ecosystem crate, so breadbar's future embedded capsule and
breadbox's overlay window can share one implementation instead of two
(THEME_SYSTEM_PLAN.md Phase 4/6a). The GTK4 results-list widget lives
behind a `gtk` feature, mirroring bread-theme's `gtk`/`adw` gating, so a
headless consumer isn't forced to link GTK.

Adds unit tests for fuzzy_score/matches_term/priority_rank, which were
previously untested pure functions inside breadbox's main.rs.
- bread-theme:🐚 WorkspacesModule.dot_widths and ClockModule.placeholder_clock,
  resolved/validated in manifest.rs and modeled in types.rs, so a theme can
  actually configure dot-pill widths and an entry-as-clock placeholder
  instead of those staying schema-only.
- New compiled-in builtin "spotlight" (assets/shell/spotlight/), registered
  in builtin.rs so list() returns three themes: centred capsule window spec
  (anchors=[top] only, width=480, exclusive=none, keyboard=on_demand),
  workspaces.style=dots, clock.style=none+placeholder_clock, launcher.mode=
  embedded, drawer=[launcher_results]. Colours are palette token names
  (pink), never hex.
- bread-theme::anim::spring_to: a reusable TickCallback size-interpolation
  helper (GTK4 has no CSS width/height transition on a widget or layer-shell
  surface), generalized from WorkspaceTrail's existing ease/tick pattern, for
  breadbar's capsule-drawer expand/collapse.
- bread_launcher::LAUNCHER_APP: the launcher's one shared on-disk identity
  (cache/config/history), so breadbar's embedded capsule and breadbox's
  overlay window read and write the SAME cache and launch history rather
  than forking into two rankings of a user's apps.

Tests: +6 spotlight builtin tests (loads/in list/capsule window shape/
embedded mode/dots widths/flat-pink-not-hex), bread-theme --lib 63->69,
bread-launcher --lib unchanged at 18.
breadbar's src/screenshot.rs gained capsule-collapsed/capsule-expanded
(Phase 6b, feature/theme-spotlight) — mirror them here so the orchestrator's
--view filter and full-suite runs actually cover them instead of rejecting
the names as unknown.
[launcher].modes now lists calc/cmd/url alongside apps, sections is
enabled, and two new keys (search_width/search_radius) let an embedded
launcher theme declare its search-state capsule geometry, defaulting to
the idle value when a theme omits them. Also gives spotlight's [bar.slots]
a widget:left_of_stats entry so a Lua widget requesting that placement
(e.g. git-branch-widget.lua) has a home instead of being silently dropped.
New query module: parse_query splits a leading =/>/. into calc/cmd/url
(falling through to a literal apps query otherwise), eval_calc is a small
four-function arithmetic evaluator matching the demo's evalCalc semantics
(bad expr/err/∞ included), and builtin_commands/filter_commands cover the
> palette. matching::split_sections groups already-sorted entries into
recent/apps using LaunchHistory's existing counts, no new tracking.

ResultsList::new takes a new `sections` bool: true builds non-selectable
"Recent"/"Apps" header rows (build_header_row) ahead of their groups,
visible only in the idle empty-query view; set_query/select_next/
select_prev all learned to treat a header row (no DesktopEntry data) as
never-selectable. breadbox passes sections=false to keep its own overlay
exactly as it was.
Phase 6c screenshot coverage for spotlight's new query sections and =
calc mode, alongside the existing capsule-collapsed/capsule-expanded pair.
matches_term advanced its scan with start = i + 1 — one byte past the START
of a failed match rather than past the whole match. Both i and i + tlen are
guaranteed char boundaries; i + 1 is not, so a multi-byte term that failed
the word-boundary check left start inside a character and the following
field[start..] slice panicked.

matches_term("café", "é") reproduces it: byte index 4 is not a char
boundary. The path is reached from priority_rank over real .desktop Name=
values, so any non-ASCII application name could crash the launcher's sort —
never hit locally because every installed app here happens to be ASCII.
128 files / ~111k lines of local graphify and grok cache were committed by an
over-broad `git add -A`. Neither is build output or source; both are
machine-local tool state. breadbar already excluded graphify-out; this repo
had no such rule.
spring_ease's backOut overshoot (peaks ~1.065) drove a shrinking
animation's interpolated value below its target, which could go
negative for a size request and trip GTK's height >= -1 assertion.
Previously only patched at breadbar's call sites; clamp inside
spring_to itself so the safety is automatic for every caller.
LaunchHistory::save silently swallowed write errors; now shared by two
hosts (breadbox + breadbar's capsule), a broken cache dir silently
stopped recording launches for both. Log on failure instead.

do_launch/emit_launched's app_id parameter is the caller's bread
event-namespace id (breadbox passes "box"), not LAUNCHER_APP
("breadbox", scoped to cache/history paths only) - two similarly
named but distinct identities. Make the doc comments say so explicitly
and add tests pinning the exact namespace-check relationship, since
confusing them silently drops the emitted event.
anchor was the one enum-shaped schema field taken via
unwrap_or_default() with no manifest-time validation, while width and
layer both bail! on an unrecognized value (as does bar.window.anchors).
A typo'd anchor silently fell through to breadbar's own runtime
eprintln + unanchored-window fallback instead of the load-time hard
error every other typo'd key gets. Validate against the three shapes
breadbar/src/surface.rs actually implements.
watch() panicked (.expect) if monitor_directory failed (e.g.
fs.inotify.max_user_watches exhaustion), contradicting the crate's own
'the shell must never fail to start because a theme file is malformed'
stance. Now logs and continues without a watch, same posture as
bread_theme::gtk::watch_theme_file's existing .ok()? handling.

watch() also resolved the active theme's directory once and pinned a
monitor there forever: switching shell.toml's active key while a host
was running left edits to the newly-active theme unobserved until
restart. Now also watches shell.toml's own directory and re-arms the
theme-directory monitor when active changes.

Public API change: shell::watch now returns ThemeWatch instead of a
bare gio::FileMonitor (still opaque, still just needs to be kept
alive). breadbar's theme.rs:525-531 stores the return value in a
RefCell<Option<gio::FileMonitor>> and wraps it in Some(monitor) - both
need updating to RefCell<Option<bread_theme:🐚:ThemeWatch>> and
*cell.borrow_mut() = monitor (no Some(..)), left to the breadbar-side
agent since this repo doesn't touch breadbar's files.

's role is now documented explicitly on watch() as a
single-process testing override, not the coordination mechanism -
shell.toml's active key is.
Neither doc site previously said which of the two theme-selection
mechanisms is meant to coordinate multiple already-running processes.
The env var is read once per process and cannot do that; shell.toml's
active key is the one every host reads, so it's the only one that can.
audit-schema-consumption.md's recurring defect: a key that validates
but does nothing, five separate instances (css/extra.css overlay,
tokens.font_family, tokens.font_fallback, tokens.font_size_base,
tokens.accent_to). All five are consumed only by breadbar/breadbox,
which this pass doesn't touch, so per the audit's own (a)/(b)/(c)
choice this is (b): document the gap at both the schema accessor and
every theme.toml site that sets the key, so the manifest states its
design intent honestly instead of implying the value has an effect it
doesn't.

Also documents modules.clock.format/show_date (consumed only by
ClockStyle::Plain, silently ignored by Flip/None) and
bar.window.margin.bottom (parsed, never applied by breadbar) the same
way.
The two built-in launcher themes previously shared the same row radius,
padding, icon treatment, and selection fill (only mode/width/top/radius/
icon_px were themed), so breadbox's overlay rendered as one launcher
recoloured rather than two different instruments.

Adds row_radius, row_inset, row_padding_v/h, icon_radius, search_font_size,
search_padding_v/h, and selection_alpha to [launcher], all consumed by
breadbox's build_css in the paired breadbox commit. Sets liquid-motion to
the soft/roomy demo numbers (row radius 12, 28px icons, 22% selection alpha,
Recent/Apps section headers) and glass-workbench to the dense/technical demo
numbers (row radius 6, 22px icons, 28% selection alpha, flat list).

Also wires up three previously-declared-but-unconsumed tokens
(font_family, font_fallback, font_size_base) for breadbox's launcher panel
specifically — scoped consumption, not the full ecosystem-wide font system
replacement (breadbar/stylesheet() are unaffected). Updated the stale
doc comments in types.rs/mod.rs and both theme.toml files to match, and
updated the two existing launcher-geometry tests plus added a glass-workbench
equivalent.

Defaults for all new keys reproduce breadbox's pre-redesign hardcoded CSS
values, so themes that don't set them (spotlight's capsule doesn't read any
of this) are unaffected.
breadbar had to hardcode past these: the manifests carried pre-review values
(chip_height 32/20/36, dot_widths [6,10,14,18]) that disagreed with what the
bar actually draws, so a chrome pass overrode them locally rather than let the
manifest dictate wrong geometry.

Updated to what the user approved on the interactive reference in
bos-ui-demos/proposed/: one chip highlight height per bar (26 island, 22 flush,
22 capsule) and Option-B dot widths [8,13,17,22]. Two tests pinned the old
numbers and are updated with the reason.

A manifest that states one thing while the app draws another is the defect
class this project has hit repeatedly; the local hardcodes in breadbar can now
be removed in favour of these.
tokens.bg_alpha governs the bar, which stays readable at 0.72 because it
covers a thin strip of wallpaper. A full launcher panel at that alpha washes
out badly over a bright wallpaper and its text becomes hard to read — which
is what breadbox looked like, since it hardcoded 0.60 and read no theme value
at all.

Adds [launcher].panel_alpha, set to the approved reference's own values: 0.95
for glass-workbench, 0.93 for liquid-motion.
Fourth compiled-in shell theme (bread-theme/src/shell/builtin.rs). Stresses
four axes no existing theme touched:

- light surfaces (new tokens.light flag, swaps which of the fixed @bg/
  @on-bg pair plays paper-surface vs ink — see Tokens::light's doc comment)
- bottom anchoring (bar.window.anchors = ["bottom", ...])
- segmented bar chrome (new bar_border = "segmented" value: the bar window
  itself draws no fill/border, three slot-group pills draw their own)
- blur disabled per-theme ([compositor.*] blur = false everywhere)

Also: a new bottom_right surface anchor (manifest.rs + types.rs), since the
three existing anchor shapes all assume a top-anchored bar's satellites
belong in the top-right corner. tokens.accent2() gives the equaliser its
own accent independent of accent_from/accent_to.
Toolchain drift (clippy 0.1.97): pre-existing on main, all mechanical
and behaviour-preserving.

- bread-theme/gtk.rs: `type AppCssBuilder` alias for the repeated
  `Rc<dyn Fn(&Palette) -> String>` (type_complexity ×4).
- bread-theme/lib.rs: `sort_by_key(|..| Reverse(len))` (unnecessary_sort_by).
- bread-theme/output.rs: `io::Error::other`; struct-init in a test.
- bread-utils/singleton.rs: explicit `.truncate(false)` on the lock file
  open — we only clear it after winning the lock (suspicious_open_options).
- bread-polkit/identity.rs: elide `pick_user` lifetimes.
- The agent prompt's username field is user-editable. It was passed
  straight to `auth::authenticate`, so a request scoped to specific
  accounts (e.g. root only) could have its PAM conversation redirected
  to any local user. `resolve_user` now accepts only the `unix-user`
  identities from `BeginAuthentication` (empty = the prefilled default);
  anything else re-shows the prompt with an explanation. PAM still has to
  clear polkit's own authorization, but this closes the foot-gun at the
  one place the identity list is known.
- A failed single-instance lock now exits(1) instead of continuing: a
  second agent would `serve_at` the same object path, and a prompt held
  by a process that couldn't take the lock is ambiguous state.
`fetch_verify_write` treats a download/checksum failure as a soft
warning and returns Ok without writing — but the `NamedTempFile` is
already created (0 bytes). Gating extraction on `exists()` let the
empty file reach `tar tvf`, which bailed with a misleading "not in
gzip format" and aborted the install. Gate on non-empty instead.
- `XDG_CONFIG_HOME` / `XDG_RUNTIME_DIR` are honoured only when absolute
  (XDG spec; matches `bread_utils::xdg`), in layerrules and output.
- `output::atomic_write` pid-suffixes its temp name (`.<name>.tmp.<pid>`)
  so two concurrent `bread-theme generate-output` runs writing the same
  `themes/<output>.css` can't race on one shared `.tmp`.
- `hypr::socket_path` reconstructs `/run/user/<uid>` from the process's
  real uid when `XDG_RUNTIME_DIR` is unset, instead of assuming 1000.
- `bread-theme --help` prints to stdout (pipeable); usage on an error
  path (missing args) goes to stderr.
Breadway deleted branch feature/theme-spotlight 2026-08-31 15:41:45 +08:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Breadway/bread-ecosystem!4
No description provided.