Add Live Activities as configurable bar items (#30)

* Add Live Activities as configurable bar items

* Fix activity owner monitoring and Morph list interactions

* Keep activity rows stable during updates and tighten their layout

* Use subtle theme tint for activity cards

* Tighten activity title and progress spacing

* Use theme accent for active progress rings

* Fit compact activities to measured titles and badge

* Expose Live Activities display controls in Settings

* Open activity terminals and streamline the Morph header

* Give Morph continuous icons and prioritize workspace navigation

* Show activity progress inside its ring

* Inset activity progress rings and strengthen their stroke

* Fix activity lifecycle, command output handling and list navigation

* Reduce activity update work and harden publisher lifetimes

* Set up Live Activities for the detected user shell during installation

* Document Activities setup, daily use and troubleshooting

* Preserve Morph idle width across empty activity updates

* Fix dimming flicker, hide inactive music tab and consolidate Activities docs

* Fix panel clearance across styles and match previews to theme settings

* Prevent panel blur from repainting neighboring islands

---------

Co-authored-by: Samuel Onoja <[email protected]>
This commit is contained in:
Samuel OnojaandSamuel Onoja authored and GitHub committed 2026-10-12 22:52:25 +01:00
1 parent 7a7df0106c
commit 6b0d903ad0
107 files changed
+9340 -291

No files matched your search

+1
View File
@@ -10,6 +10,7 @@
## Use the desktop
- [Live Activities](activities.md): show running commands in the panel, configure detection and troubleshoot missing tasks.
- [Shortcuts and gestures](shortcuts.md): keyboard navigation, moving and resizing with Super-drag, and window grouping.
- [Lock screen](locking.md): change its appearance, set up automatic locking and configure display sleep.
- [Screen sharing and recording](screen-sharing.md): choose a source and stop sharing.
+406
View File
@@ -0,0 +1,406 @@
# Live Activities
Live Activities shows running commands and other tasks in your panel. A build
can stay visible while you work in another window; click its activity to return
to the terminal when Ferese can identify that window. The panel supplies the
appearance, so tasks use your theme and animation settings.
## Start using it
After installing with `./scripts/install.sh`, open a new terminal and run a
command as usual:
```sh
cargo build
```
The installer detects Fish, Bash or Zsh, installs its shell hook, enables shell
activities and adds an Activities item to your panel. A Cargo build appears
once it has run for **1.5 seconds**. A cached build that finishes sooner stays
hidden, including its completion message.
These command rules are included:
| Command | Delay before appearing |
| --- | --- |
| `cargo build`, `check`, `test`, `run`, `clippy` | 1.5 seconds |
| `npm` or `pnpm` followed by `install`, `build`, `test`, `run` | 1.5 seconds |
| `make` | 300 ms |
| `git push` | 300 ms |
| `docker build` | 1.5 seconds |
Rules match the command text at the start of the line. Commands such as
`cargo +nightly build`, `npm ci`, or `env RUSTFLAGS=... cargo build` need a custom
rule or an explicit wrapper. Commands that don't match a rule are discarded
without being logged.
To check the display without waiting for a build, run:
```sh
feresectl run --title "Activity check" -- sleep 5
```
This publishes directly and doesn't need a shell hook, but the panel still needs
an Activities item. The check appears after 300 ms and finishes after five
seconds.
### Existing installations
From an updated Ferese checkout, rerun user setup as your normal user:
```sh
python3 scripts/installer/install.py user-setup
```
This needs the updated `feresectl` installed. Setup backs up changed config and
shell startup files. It preserves an explicit `shell-hook #false` setting and
an existing Activities item, including one you've hidden. After the first
successful setup, later installs update the hook files without restoring panel
items you've removed or changing your activity settings.
Open a new terminal to load the hook. In an existing Fish terminal, you can
load it immediately with:
```fish
source ~/.config/fish/conf.d/ferese-activities.fish
```
The paths above use the default configuration directory. If you set
`XDG_CONFIG_HOME`, use that directory in place of `~/.config`.
### Add Activities to your panel
Open **Settings → Panel & Shell → Edit panel**, select a group in Arrange, and
choose **Add item → Activities**. Keep its visibility enabled. This is also how
to restore the item after removing it.
Activities can live in Start, Center or End. Regular panels use that position
and open the activity list in a popup. Morph presents tasks inside its existing
island and adds an Activities tab. Changing a group's position doesn't create
another Morph island. See [Panel configuration](panel-configuration.md) for item
placement and overflow controls.
For manual panel configuration, add an `activities` item to an existing group.
This complete panel example places it at the end of a floating panel; don't add
a second `panel` block if your config already has one:
```kdl
panel "main" {
style "floating"
end {
group "tasks" {
item "activities" kind="activities" priority=20
}
}
}
```
### Manual shell setup
Without installer setup, hooks and process watching are off by default. Add an
Activities item to the panel and enable `activities.sources.shell-hook` before
loading a hook. From a checkout, source `contrib/activities/ferese.fish` in Fish,
`contrib/activities/ferese.zsh` in Zsh, or the following in Bash:
```sh
source contrib/activities/bash-preexec/bash-preexec.sh
source contrib/activities/ferese.bash
```
Use absolute paths when adding these lines to shell startup files. Zsh uses
`add-zsh-hook`, Fish uses named pre/postexec events, and Bash composes with
[bash-preexec](https://github.com/rcaloras/bash-preexec)'s hook arrays. Load
bash-preexec before the Bash hook; the hook does not replace an existing DEBUG
trap. Hook files never execute captured command text and preserve command status.
The installer copies hooks into `$XDG_CONFIG_HOME/ferese/activities`, defaulting
to `~/.config/ferese/activities`. Fish loads through its `conf.d` directory; Bash
and Zsh get a marked block in `.bashrc` or `.zshrc`. Zsh setup honors an exported
`ZDOTDIR`. Bash shells started in login mode must also load `.bashrc` from their
login profile for the installed hook to run.
Process watching is an opt-in alternative when hooks are unavailable. It performs
bounded sampling on one worker, and disappearance means expiry, never inferred
success or failure. Shell hooks observe interactive commands; they do not
automatically wrap them or parse their output. The
[Cargo adapter](#cargo-progress) requires `feresectl run`.
## Run a command explicitly
Use `feresectl run` for commands outside the built-in rules, unsupported shells,
or scripts where an interactive hook doesn't run:
```sh
feresectl run --title "Backup" -- ./backup.sh
feresectl run --title "Tests" -- npm test
```
Arguments after `--` are passed to the command. It keeps its terminal input,
output, working directory, environment, terminal process group and exit/signal
status, and still runs if activity publishing fails. Completion follows the command's exit. A background descendant holding
stdout open does not keep the wrapper alive indefinitely.
### Cargo progress
A normal `cargo build` shows that the command is running and whether it finishes
successfully. It doesn't report a percentage. To show the number of compilation
units completed, use the Cargo adapter:
```sh
feresectl run --cargo-json -- cargo build
```
The adapter reads Cargo's JSON output while keeping diagnostics readable in the
terminal. It reports completed units as text because Cargo doesn't supply a
reliable total for a percentage. Setting `progress "cargo-json"` in a rule enables
this adapter for matching `feresectl run` commands; it does not wrap plain
`cargo build` automatically. A `build-finished` message never completes the
activity; completion follows the actual child exit. The adapter also preserves
non-JSON output. See the
[Cargo JSON contract](https://doc.rust-lang.org/cargo/reference/external-tools.html).
## Use the activity list
Morph shows up to two activities in its collapsed and peek states, with a `+n`
badge for additional tasks. Higher-priority tasks appear first, with temporary
boosts for new tasks and completion results. Hovering holds the current selection
in place while its values continue updating. Recording and low battery take
priority over ordinary activities.
Click the activity strip or open the Activities tab to see the list. Arrow keys
select a row and Enter activates it. Selecting a linked task focuses its terminal
and closes the list. If the terminal has closed, or one terminal process owns
several possible windows, Ferese leaves the list open. It cannot select a
particular tmux pane.
Tasks with reported progress show a percentage inside a ring. Tasks without a
percentage show an animated arc around their icon. Reduced motion makes progress
changes immediate and uses a static track. Activities do not automatically open
the expanded panel.
A visible task's result stays for 1.5 seconds by default. Do not disturb limits
ordinary running activities to one, hides the badge and suppresses result
flashes. When there are no eligible tasks, the regular Activities item has no
content and Morph returns to its other content.
## Configure Activities
In **Settings → Panel & Shell → Appearance**, **Visible activities** chooses one
or two slots in Morph's collapsed and peek states. **Show activity count** controls
the `+n` badge; hiding it still lets you open the full list.
For source and timing settings, edit the top-level `activities` block in
`~/.config/ferese/config.kdl`. Merge these fields into an existing block rather
than adding a second one:
```kdl
activities {
max-visible 2
show-badge #true
start-delay-ms 300
result-flash-ms 1500
sources {
shell-hook #true
process-watch #false
notification-bridge #false
}
}
```
| Setting | Effect |
| --- | --- |
| `max-visible` | One or two compact activity slots. |
| `show-badge` | Show `+n` for tasks beyond those slots. |
| `start-delay-ms` | Minimum delay for a new task; a rule can require a longer delay. |
| `result-flash-ms` | How long an eligible completion result remains visible. |
| `shell-hook` | Accept command start/end events from an installed shell hook. |
| `process-watch` | Opt-in process sampling when hooks aren't available; process disappearance removes the task without inferring success or failure. |
| `notification-bridge` | Present notifications as activities instead of duplicate toasts, respecting Do not disturb and notification popup settings. |
The process watcher and notification bridge stay off unless you enable them.
Media comes from MPRIS; downloads and call statuses need an integration that
publishes them. Microphone or camera use alone does not create a call activity.
Changes reload live. Invalid edits leave the last valid configuration in use;
check an edit with `ferese --check-config ~/.config/ferese/config.kdl`.
For command patterns, custom titles, per-rule delays and completion messages,
see [Command rules](#command-rules).
### Turn off automatic command detection
Set `shell-hook #false` inside `activities.sources`. Leave `process-watch` off
as well if you don't want process detection. You can still use `feresectl run`
and direct publishers.
To skip shell and Activities setup during installation, pass
`./scripts/install.sh --no-activities`. This skips setup for that installation;
it doesn't remove an existing hook or disable an already configured source.
### Command rules
```kdl
activities {
max-visible 2
show-badge #true
start-delay-ms 300
result-flash-ms 1500
sources {
shell-hook #true
process-watch #false
notification-bridge #false
}
rule "cargo" {
match #"^cargo (build|check|test|run|clippy)\b"#
title "cargo {1}"
icon "hammer"
priority 100
progress "indeterminate"
min-duration-ms 1500
ignore-parents "rust-analyzer"
dedupe-by "cwd"
flash-on-fail #true
result ok="Finished" fail="Command failed"
}
}
```
User rules run in declaration order, then unreplaced built-ins in Cargo,
npm/pnpm, make, git-push, docker-build order. A user rule replaces the entire
built-in with the same name. One command selects one rule, pinned for its lifetime.
`ignore-parents` accepts multiple names. Regex input/pattern/program sizes are
bounded. Command and cwd metadata are used only during matching; nonmatches are
discarded and never logged. Grouping hashes cwd within owner/rule. Invocations
retain independent lifetimes and progress is never averaged.
The global/rule visibility delay is their maximum. Completion before that delay,
including failure, never flashes. Eligible results last 1500 ms by default;
`flash-on-fail #false` removes failures directly. Configuration is validated
before application; an invalid reload preserves the last valid configuration.
Diagnostics include source offsets for activity configuration errors.
## If nothing appears
1. **Try the five-second check above.** If it appears, the display and broker
work; check shell setup and command matching next.
2. **Check the panel item.** In Arrange, confirm that Activities exists and is
visible. On a regular panel, also check its overflow and available space.
3. **Open a new terminal.** Installing a hook doesn't load it into terminals
that are already open. Confirm `shell-hook #true` in your config too.
4. **Check the command and duration.** Cargo and npm tasks must last at least
1.5 seconds under the built-in rules. A custom rule can increase that delay.
Prefixes and different subcommands may not match the built-in patterns.
5. **Inspect the broker while the task runs:**
```sh
feresectl -j activity list
```
An empty `activities` array means no task is registered in that session.
A registered task can still be waiting for its delay, ranked below another
task, or hidden by panel settings. In Morph, also check for recording or a
low-battery state taking priority.
6. **Check the session and build.** A nested preview has its own broker; use the
same [session socket](ipc.md#choose-a-session) for publishing and listing.
If `activity` or `run` is an unknown command, update the installed binaries.
Log out and back in after upgrading the compositor and shell.
Shell hooks suppress publishing errors so they don't interrupt your commands.
For an ownership or permission error, use a direct publication from the same
terminal to see the response:
```sh
feresectl activity start diagnostic --title "Activity check" --lease-ms 10000
feresectl activity cancel diagnostic
```
The lease removes this diagnostic after ten seconds if you omit the cancel.
Ownership verification needs Linux 6.5 or newer. See
[Publisher ownership and lifecycle](#publisher-ownership-and-lifecycle)
for ownership, limits and expiry behavior.
## Publish your own status
Run these commands from the same shell. The ID `download` belongs to that shell,
so another terminal cannot update it:
```sh
feresectl activity start download --source download --icon download \
--title "Downloading update" --progress 0.2
sleep 2
feresectl activity update download --progress 0.7
feresectl activity end download --result ok --message "Downloaded"
```
This example publishes status only; it doesn't perform a download. Your program
supplies real progress and completion results.
## Publisher ownership and lifecycle
Use the same socket selection as every other control command: `--socket`, then
`FERESE_SOCKET`, then the session runtime default. Separate nested sessions keep
separate brokers. One-shot commands use their verified immediate parent (normally
the shell), not the CLI connection, as lifetime owner. IDs are local to that owner.
A direct publisher and `run` use the publishing process itself. The host obtains
kernel peer credentials and a socket-bound pidfd, checks the parent relationship,
process birth and UID, and monitors owner exit. This requires Linux 6.5 or newer;
an unverifiable owner is rejected instead of trusting a JSON PID.
```sh
feresectl activity start build --icon hammer --title "Build"
feresectl activity update build --progress 0.6
feresectl activity end build --result ok --message "Built"
feresectl activity start tea --icon timer --title "Tea" --remaining-ms 180000
feresectl run --title "cargo test" -- cargo test
```
`start` prints a JSON handle containing owner, session and generation. The
`--handle` option on update/end/cancel checks that exact invocation. Omitting it
resolves the local ID only within the same verified owner. Duplicate starts are
rejected. Repeated identical accepted results keep the original result deadline;
a removed or superseded generation is stale. Register again after broker restart.
The compositor assigns a terminal window at activity creation using the
publisher's process ancestry. List selection follows the activity ID across
reordering. Recording and low battery keep their protected priority, including
the combined recording/low-battery presentation.
Each owner may hold three records, including delayed and terminal entries. The
session defaults to 128 records, a priority ceiling of 160 and two compact slots.
New publications are limited to ten per owner per second. Replaceable updates
are limited to 30 per owner per second; rejected updates get
an explicit error. The bar coalesces snapshots through a bounded latest-value
subscription while the broker retains accepted results. Terminal/cancel dispatch waits for bounded dispatcher capacity
on the socket worker. A CLI disconnect does not remove an owner's records.
Running owner death and expired leases remove entries without assigning a result.
Results survive owner exit until their original deadline. Leases are positive and
at most five minutes; valid refreshes renew only the lease.
Activities stay on their creation output, initially the focused output. On output
removal they move to the first surviving output, or become unpresented until an
output is available. Host timestamps use `CLOCK_BOOTTIME`, including suspend.
Countdown wire timestamps are ignored and replaced with host sample time.
## Extensions and templates
The version-1 activity schema is carried inside the existing versioned,
length-prefixed JSON `activity-publish` command. `activity-get` and long-poll
`activity-watch` provide snapshots. See `ferese_ipc::activities` and
`ferese_activities::Data`; there is no second socket or JSON-lines protocol.
Extensions publish typed Progress, Timer, Media, or List data. The bar controls
rendering. Values are indeterminate, finite progress in `[0,1]`, bounded text, or
countdown durations. Titles/text are limited to 256 UTF-8 bytes; lists to eight
rows. Icons are symbolic names; unknown names use the Ferese symbol, and paths
are rejected. No extension widgets, markup, colors, shaders or drawing programs
are accepted.
Use `--template` with a typed JSON template, e.g.
`'{"kind":"list","data":{"rows":["Indexing files","Waiting for server"]}}'`.
Capture/call integrations use `--source capture` and describe the state they
actually observe. Process names, microphone use, or camera use do not infer calls
or downloads. Existing MPRIS state supplies Media rows and transport controls;
recording/battery reuse the shell's existing protected state and watchers.
The optional notification bridge uses existing notification state, ranks below
all other ordinary sources, and respects DND and popup privacy. When enabled, a
notice becomes an activity rather than a duplicate toast. It is off by default.
+2
View File
@@ -7,6 +7,8 @@ and display settings. Its browser/editor shortcuts need Firefox/Zed; brightness
keys need `brightnessctl` and audio keys need `wpctl`.
This reference covers layouts, appearance, input, and desktop behavior.
See [Desktop portals](portals.md) for application integration options.
The [Live Activities guide](activities.md#configure-activities) covers task
visibility, shell detection, command rules and notification bridging.
## KDL syntax
+25
View File
@@ -27,12 +27,37 @@ host. Hardware checks remain separate.
## Test guides
- [Live Activities checks](#live-activities-checks): core, CLI, shell hooks, installer setup and native rendering.
- [Desktop reconciliation](desktop-reconciliation.md): topology publication, hardware failure and presentation ownership.
- [Output management tests](output-management-testing.md): profiles, hotplug, mirroring, lid handling and failed DRM commits.
- [Nested resize checks](nested-resize-testing.md): wallpaper and bar responsiveness.
- [Client soak testing](native-soak-testing.md): repeated client lifecycle and resource growth.
## Live Activities checks
See [Live Activities](activities.md) for setup, configuration and the publisher
protocol.
Hidden items and measurement widgets do not animate. Reduced motion snaps
progress and uses a static track. Workspace buttons retain their own actions;
activity selection does not replace workspace navigation.
```sh
cargo test --locked -p ferese-activities -p ferese-config -p ferese-ipc -p feresectl
cargo test --locked -p ferese-shell
python3 scripts/tests/test_activity_hooks.py -v
python3 -m unittest discover -s scripts/tests -p 'test_portal_install.py' -v
FERESE_TEST_ACTIVITIES=1 python3 scripts/tests/test_activities_isolated.py -v
FERESE_REVIEW_BACKEND=tiny-skia cargo test --locked -p ferese-shell \
render_hub_review_states -- --ignored --nocapture
```
The isolated check starts a private Weston host and broker. Render captures live
in `/tmp/ferese-hub-review`; they include single/split/list content, long titles,
translucency and 1×/1.25×/1.5× scales. Direct DRM, physical multi-monitor hotplug,
suspend and live capture hardware still need hardware validation.
## Portal checks
Run backend unit tests and compare D-Bus contracts against installed portal XML:
+15 -5
View File
@@ -145,16 +145,23 @@ entry, icons, default wallpaper and example config. Releases live under
`/usr/local/lib/ferese/releases/` and commands under `/usr/local/bin/`, with `current`
and `previous` links for switching between releases during updates and rollback.
The installer preserves your configuration, other desktop sessions and existing PAM
policy. Ferese starts its authentication agent and portal services with the session.
On first user setup, the installer detects Fish, Bash or Zsh, installs its
[Live Activities hook](activities.md#start-using-it), and adds Activities to your
panel. It backs up changed config and shell startup files, preserves an explicit
`shell-hook #false`, and leaves later activity and panel edits alone. Open a new
terminal to load the hook, or pass `--no-activities` to skip this setup.
Other desktop sessions and existing PAM policy are preserved. Ferese starts its
authentication agent and portal services with the session.
The authentication agent registers for the login session, so requests from desktop
apps use the Ferese dialog. Remove other polkit agents from your Ferese autostart
configuration to avoid competing session registrations.
### Initial configuration
You can start with Ferese’s built-in defaults, or copy the example config if you’d like
a file to customize:
Activities setup creates a config file if one doesn't exist. You can edit that
file in Settings. If you skipped setup, Ferese uses its built-in defaults; you
can copy the example config to start a file yourself:
```sh
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/ferese"
@@ -318,7 +325,9 @@ python3 scripts/installer/install.py user-setup
This backs up the exact old development portal override when present and reloads
user systemd configuration. It preserves custom overrides and does not restart the
running desktop or portal.
running desktop or portal. It also installs the detected shell's Activities hook
and performs first-time Activities configuration if needed. See
[existing installations](activities.md#existing-installations) for details.
Installer options:
@@ -326,6 +335,7 @@ Installer options:
./scripts/install.sh --dry-run
./scripts/install.sh --offline --release-id my-build
./scripts/install.sh --resize-metrics
./scripts/install.sh --no-activities
./scripts/install.sh --bundle-only ./target/bundles/my-build --release-id my-build
./scripts/install.sh --bundle ./target/bundles/my-build
```
+13
View File
@@ -20,6 +20,7 @@ feresectl output-profiles
feresectl theme get
feresectl xwayland status
feresectl media
feresectl activity list
```
The `get-*` spellings remain hidden aliases for the corresponding queries and
@@ -51,6 +52,18 @@ width/height with left/up and grows them with right/down by 32 logical pixels,
subject to client size limits. Fullscreen and maximized floating windows ignore
these actions.
## Live Activities
Use `feresectl run --title "Tests" -- cargo test` to publish a command's lifetime,
or `feresectl activity start`, `update`, `end` and `cancel` to publish status from
an integration. The [Live Activities guide](activities.md) covers setup and
examples; its [publisher section](activities.md#publisher-ownership-and-lifecycle) explains ownership,
leases and the `activity-publish`, `activity-get` and `activity-watch` commands.
`feresectl activity setup --config PATH` updates a local config for shell
activities and the panel item without connecting to a session. It preserves an
explicit `shell-hook #false`; the installer uses it during first-time user setup.
## Choose a session
Every feresectl IPC command, including theme commands and subscriptions, resolves
+17 -7
View File
@@ -41,6 +41,10 @@ Other controls save automatically, with sliders saving when you release them.
**Undo** restores the previous save, **Reload configuration** reads external
edits, and **Back to Panels** returns to the appearance controls.
Add **Activities** to show running commands and published task progress. Its
visibility and overflow follow the same item controls. The
[Live Activities guide](activities.md) covers shell setup and command detection.
**Remove custom panel** restores the built-in composition, style and geometry;
Undo brings back the composition you removed.
@@ -94,7 +98,7 @@ style supply it.
| `height` | 36; Morph uses 34 | Number 24–128 | Panel height, or Morph's collapsed height |
| `edge-margin` | 8 for detached styles; 0 for Notch and Edge to edge | Integer 0–4096 | Distance from the selected screen edge |
| `side-margins` | 8 for detached styles; 0 for Notch and Edge to edge | Integer 0–4096 | Requested inset at both ends of the panel |
| `window-clearance` | 0 | Integer 0–4096 | Additional reserved space between the panel and windows |
| `window-clearance` | 0 | Integer 0–4096 | Space between the panel and tiled or maximized windows |
| `inner-padding` | 12 | Number 0–64 | Space inside the row before its first and after its last group |
Notch and Edge to edge use zero edge and side margins even if you have saved
@@ -108,10 +112,10 @@ more content appears, so the margins do not cause additional overflow. Settings
limits the slider to the available display and preview measurements; KDL accepts
the wider range shown above.
Morph reserves space for its collapsed height plus window clearance. Its
expanded views overlay windows, so increasing their height does not push windows
down. At the panel edge, Morph accounts for the layout's outer gap rather than
adding the two gaps together.
All panel styles reserve their height plus window clearance. At the panel edge,
clearance replaces the layout's outer gap; the other edges keep their usual gaps.
Morph reserves its collapsed height, and expanded views overlay windows without
pushing them down.
## Background, outline and corners
@@ -247,6 +251,11 @@ content. Hovering can expand a preview, and clicking opens the hub's Workspaces,
Music, Calendar,
Notifications or Controls view. Escape or the close button collapses it.
With an Activities item configured, running tasks can occupy the collapsed and
peek states, and the hub gains an Activities tab. **Visible activities** and
**Show activity count** in Appearance control its compact slots and `+n` badge;
see [Activities configuration](activities.md#configure-activities).
The Workspaces view shows miniature window layouts. Click a tile to switch
workspace, use Tab and the arrow keys to navigate it, or drag a miniature window
to another tile on the same output. Scrolling over the collapsed hub changes
@@ -255,8 +264,9 @@ workspace, with a brief workspace preview after a switch.
Morph keeps your saved composition for when you switch back to a regular panel.
The hub uses its own layouts and compact workspace dots, so Start/Center/End
positions, group padding and the regular workspace indicator style do not apply
inside it. Item settings still determine which configured controls appear in
its Controls view and shortcuts.
inside it. `inner-padding` and `side-margins` also belong to the regular panel;
Morph uses its own content spacing and width budgets. Item settings still
determine which configured controls appear in its Controls view and shortcuts.
Turn on **Attach to screen edge** to join the hub to the chosen edge with curved
shoulders, or set `morph { attached #true; }`. **Preview on hover** maps to