# Drawer as the control center Sub-project 6 of the roadmap in `2026-10-07-island-core-design.md`. The drawer keeps its look, a notification space above a tile grid, and becomes the one control surface: the island's controls panel goes, the drawer takes its SUPER bind, and three things it lacked arrive as a performance page, a clipboard page and a quiet mode. ## Drawer shell **Keybinds**, in the live `~/.config/hypr/sections/keybindings.lua`, outside this repo, shown to the user before editing: - SUPER alone (the existing `SUPER + Super_L` release bind) runs `ipc call drawer toggle`. - SUPER+Return is removed and left free. - CTRL+SHIFT+L runs `ipc call drawer open clipboard`, replacing the `cliphist-rofi` command. **The controls panel goes.** Its seven tiles each opened a drawer page that already exists, and the user does not use it. Removed: `ControlsPanel.qml`, `ControlTile.qml`, `Controls.js`, `test_controls.js`, the controls icon and `openControls` in `StatusPill.qml`, `toggleControls` and the `"controls"` panel in `Island.qml`, the `island` IPC target in `shell.qml`, and the controls list in `Prefs.qml` (`pinnedIp` stays). The island README loses its controls section; `AGENTS.md` notes where SUPER alone now goes. **The tile grid scrolls on its own.** The `Flow` sits inside a `Flickable` whose height is capped at three rows. Past that, the tiles scroll inside the box and the notification space above keeps its size. A thin accent scrollbar shows only when there is overflow, so a fourth row is discoverable. **Order:** Sound, Network, Bluetooth, KDE Connect, Mail, Status, Performance, Clipboard, VMs, AI, Appearance. The everyday ones fill the first three rows. ## Performance module `modules/perf/PerfModule.qml`, name `perf`. - Tile line: `CPU 12% ยท RAM 45%`, with a dot coloured by `Perf.severity` when it is not ok, as on the island icon. - Page: the island's `PerfPanel` inside a `Page`. The page sets `Perf.detailed` while loaded and clears it on destruction. - `Perf.qml` and `PerfPanel.qml` stay in `island/perf/` and are imported from there. Ceiling, as a `ponytail:` comment: `Perf.detailed` has one writer at a time. The island panel and the drawer page both open, then one closed, would stop the detailed sampling under the other. The drawer covers the screen, so the two are not open together in practice. ## Clipboard module `modules/clipboard/`, name `clipboard`, no service running while the drawer is closed, no tile line. - Opening the page runs `cliphist list` once (750 entries measured, 42 of them images). - A search field holds focus on open and filters case-insensitively as you type. - A `ListView`, newest first. A text row is one elided line. An image row (`[[ binary data ... ]]`) shows a thumbnail, decoded with `cliphist decode` into `$XDG_RUNTIME_DIR/qs-clip/.png` the first time the row is created and reused after; the directory is tmpfs, cleared by a reboot. - Up and Down move the highlight, Enter copies the highlighted entry, Escape behaves as on every page. - A click or Enter copies and closes the drawer: `sh -c 'printf %s "$1" | cliphist decode | wl-copy' sh `. The entry is an argument, never part of the command string: clipboard text is untrusted. - An `x` on each row runs `cliphist delete` with the line on stdin the same way, and the row goes. - A "Clear all" button in the page header becomes "Confirm?" for 3 seconds; the second click runs `cliphist wipe` and empties the list. ## Quiet mode A new mode in the status registry: mutes the Claude and opencode sounds and leaves their popups alone. - `shared/Status.qml`: a `ModeFile` on `status.quiet` and `Status.quiet`. Presentation saves quiet and sets it on, and restores it when it ends, like dnd and nolock. - `statusctl`: `quiet` joins `MODES`. `test-statusctl.sh` gains a quiet check. - Status page: a "Quiet" row, "Mutes Claude and opencode sounds. Popups still appear." The presentation row's description becomes "Do not disturb, quiet, no screen lock, breaktimer paused." `desktop/modules/status/notify-sound`, symlinked into `~/bin` like `statusctl`. It exits at once when `statusctl quiet get` prints 1; otherwise it maps its argument to a file under `~/Music/notify/` and plays it with `paplay`: | argument | sound | | --- | --- | | `claude` | `claude-attention.opus` | | `permission` | `opencode-perms.opus` | | `complete`, `error`, `question` | `opencode-attention.opus` | | anything else | nothing | Ignoring unknown arguments matters: the opencode notifier runs its command for every event, `session_started` and `user_message` included. Live configs, outside this repo, shown to the user before editing: - `~/.claude/settings.json`: the `Notification` hook command becomes `notify-sound claude`. - `~/.config/opencode/opencode-notifier.json`: `"sound": false` on permission, complete, error and question, and `"command": { "enabled": true, "path": "notify-sound", "args": ["{event}"] }`. The plugin reloads this file on every event, so no restart is needed. Notifications stay as they are. Behaviour change: error and question played the plugin's bundled default sound and now play `opencode-attention.opus`. ## Testing - `desktop/modules/status/test-statusctl.sh`, with the quiet check. - `desktop/modules/status/test-notify-sound.sh`: a fake `paplay` first on `PATH` records its argument; asserts the file per argument, nothing for an unknown one, nothing while quiet is 1. `XDG_RUNTIME_DIR` points at a temp directory so the real mode is untouched. - On screen, by the user: tile scrolling, SUPER alone, the performance page, clipboard search, copy (text and image), delete and clear all, the quiet row against a real Claude notification, presentation turning quiet on. New files and directories under `desktop/` need `./reload.sh desktop`, not a hot reload, and so does the `shared/Status.qml` edit.