docs(quickshell): correct CLAUDE.md's config-path and architecture claims
The file said ~/.config/quickshell is a symlink to this repo and that saving a file hot-reloads the running shell. Both are wrong and cost real debugging time: hosts/terra/home.nix ships the tree with xdg.configFile."quickshell", which COPIES it into the store, so the config dir is a read-only symlink into /nix/store and the running shell watches the frozen copy. Hot reload only applies to a working-tree instance, which is what `nix develop` + qs-dev is for. Also records the trap that follows from it: a new file must be git-added before it can be deployed at all, because flakes read the git tree and drop untracked files silently — producing a deployed config that fails to load, which does not surface until the next restart because the running shell keeps serving the store path it resolved at launch. Architecture section was stale independently of that: shell.qml has not instantiated Bar/BarTop/BarBottom for a while, there are eleven launcher variants rather than eight, the HyprChrome tree was undocumented, and there are TWO Theme singletons — a token added to one does not exist in the other. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GAq2kKCLazZmrKvkd3akud
This commit is contained in:
@@ -4,38 +4,122 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## What this is
|
## What this is
|
||||||
|
|
||||||
A [Quickshell](https://quickshell.org/) configuration — a QML-based Wayland desktop shell (bar, launcher, tray, decorations) for a Hyprland/wlroots setup. `~/.config/quickshell` is a symlink to this repo, so Quickshell loads `shell.qml` here as the "default" config.
|
A [Quickshell](https://quickshell.org/) configuration — a QML-based Wayland desktop shell (bar, launcher, tray, decorations) for a Hyprland/wlroots setup. `shell.qml` is the entry point.
|
||||||
|
|
||||||
## Running / testing changes
|
## Running / testing changes
|
||||||
|
|
||||||
|
**`~/.config/quickshell` is NOT a symlink to this repo.** `hosts/terra/home.nix`
|
||||||
|
ships the tree with `xdg.configFile."quickshell"`, which COPIES it into the nix
|
||||||
|
store, so the config directory is a read-only symlink into `/nix/store/...`.
|
||||||
|
Editing a file here therefore changes nothing about the running shell: it is
|
||||||
|
watching the frozen store copy, and every edit would otherwise cost a
|
||||||
|
`nixos-rebuild`. Two consequences worth knowing before debugging anything:
|
||||||
|
|
||||||
|
- **A new file must be `git add`ed before it can be deployed at all.** Flakes
|
||||||
|
read the git tree, and untracked files are silently dropped — with no warning
|
||||||
|
and no eval error. An untracked module that `shell.qml` imports produces a
|
||||||
|
deployed config that fails to load, which does not surface until the next
|
||||||
|
restart because the running shell keeps serving the store path it resolved at
|
||||||
|
launch.
|
||||||
|
- To check what a deploy actually shipped, compare the evaluated source with
|
||||||
|
what is live:
|
||||||
|
`nix eval --raw '.#nixosConfigurations.terra.config.home-manager.users.darman.xdg.configFile."quickshell".source'`
|
||||||
|
then `ls` that path against `ls -l ~/.config/quickshell`.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
qs # runs ~/.config/quickshell/shell.qml (this repo, since it's the symlinked default config)
|
nix develop # then: qs-dev — swap the running shell for the WORKING TREE, no rebuild
|
||||||
qs -p . # run this directory explicitly regardless of symlink
|
qs # runs the packaged (store) config
|
||||||
qs -n # exit immediately if another instance is already running (use to avoid duplicate shells while iterating)
|
qs -p . # run this directory explicitly
|
||||||
|
qs -n # exit immediately if another instance is already running
|
||||||
|
qs kill # kill the default-config instance ('qs kill -p <path>' for a working-tree one)
|
||||||
```
|
```
|
||||||
|
|
||||||
Quickshell hot-reloads QML on file save when already running, so for most edits just save and check the running instance rather than restarting `qs`. It watches file CONTENT: `touch` alone never reloads (mtime is not a change), while any real edit does, including inode-replacing ones (`sed -i`, `perl -i`). A save that is not picked up leaves the shell rendering the previous config with no error — `qs log` shows a `Reloading configuration...` line for every save it saw, so that is the check. There is no separate build/lint/test tooling in this repo — verification is visual/behavioral via the running shell. `qmlls` (QML language server) is configured via `.qmlls.ini` for editor diagnostics.
|
`qs-dev` is the edit-save-see loop: it starts a working-tree instance, waits
|
||||||
|
until it is confirmed up, and only then kills the packaged one, so a QML error
|
||||||
|
leaves you on your normal bar instead of no bar. It is a SWAP rather than a
|
||||||
|
second instance because quickshell keys instance identity on the config path —
|
||||||
|
two instances would both map layer-shell bars onto every output. See the
|
||||||
|
`nix develop` block in `flake.nix`.
|
||||||
|
|
||||||
|
Pointed at the working tree, quickshell hot-reloads on file save. It watches
|
||||||
|
file CONTENT: `touch` alone never reloads (mtime is not a change), while any
|
||||||
|
real edit does, including inode-replacing ones (`sed -i`, `perl -i`). A save
|
||||||
|
that is not picked up leaves the shell rendering the previous config with no
|
||||||
|
error — `qs log` shows a `Reloading configuration...` line for every save it
|
||||||
|
saw, so that is the check.
|
||||||
|
|
||||||
|
A single component can also be run in isolation, which is the way to exercise
|
||||||
|
something that owns a service or a surface without bringing up the whole rail:
|
||||||
|
point `qs -p` at a scratch directory whose `shell.qml` instantiates only that
|
||||||
|
component, with the repo's directories symlinked in for the `qs.` imports.
|
||||||
|
|
||||||
|
There is no build/lint/test tooling wired up in this repo, but two things are
|
||||||
|
worth reaching for. `qmllint` (from qtdeclarative) catches syntax and binding
|
||||||
|
errors without a compositor — expect noise from the synthesized `qs.*` modules
|
||||||
|
and the `Theme` singleton, which it cannot resolve:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
qmllint -I <qtdeclarative>/lib/qt-6/qml -I <quickshell>/lib/qt-6/qml -I . <file>.qml
|
||||||
|
```
|
||||||
|
|
||||||
|
And `tools/quickshell-preview/render.sh` renders an `Item`-rooted component to
|
||||||
|
a PNG offscreen (see `tests/`). Keep production `PanelWindow` wrappers thin and
|
||||||
|
put the visuals in an `Item` so they can go through that path. `qmlls` is
|
||||||
|
configured via `.qmlls.ini` for editor diagnostics.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
`shell.qml` is the entry point: a `Scope` that instantiates the top-level pieces — `Bar`, `BarBottom`, `BarTop`, and a hidden `Launcher` — as siblings. Each top-level widget manages its own `PanelWindow`(s); there's no central layout manager.
|
`shell.qml` is a `Scope` instantiating the top-level pieces as siblings:
|
||||||
|
`HyprChromeShell` (the status rail), the eleven launcher variants,
|
||||||
|
`Notifications`, `VolumeOsd` and `Vitals`. Each manages its own
|
||||||
|
`PanelWindow`(s); there is no central layout manager.
|
||||||
|
|
||||||
|
The one exception, and the pattern to follow for anything new that needs it, is
|
||||||
|
`HyprChromeShell`: it owns the state its surfaces have to AGREE on rather than
|
||||||
|
letting each decide — which monitor they live on, the rail's density, whether a
|
||||||
|
polkit prompt is open, and the layer pair. A second reader is what makes a
|
||||||
|
property shell state; the file's own header comment enumerates them and says
|
||||||
|
why each qualifies. Layer levels in particular are derived TOGETHER, because
|
||||||
|
two surfaces on one layer stack by creation order while one layer apart is a
|
||||||
|
guarantee.
|
||||||
|
|
||||||
**Import convention**: QML modules are imported by their path under the repo root using the `qs.` namespace, e.g. `import qs.widgets.launcher`, `import qs.widgets.decoration`. Sibling files in the same directory are imported with a relative string import instead (e.g. `Bar.qml` does `import "modules"`).
|
**Import convention**: QML modules are imported by their path under the repo root using the `qs.` namespace, e.g. `import qs.widgets.launcher`, `import qs.widgets.decoration`. Sibling files in the same directory are imported with a relative string import instead (e.g. `Bar.qml` does `import "modules"`).
|
||||||
|
|
||||||
**Multi-monitor**: Bar/BarTop/BarBottom each wrap their `PanelWindow` in `Variants { model: Quickshell.screens }`, so one window instance is created per connected screen. `pragma ComponentBehavior: Bound` + `required property var modelData` is the standard pattern for these per-screen delegates.
|
**Multi-monitor**: a surface that must exist on every screen wraps its
|
||||||
|
`PanelWindow` in `Variants { model: Quickshell.screens }`, one instance per
|
||||||
|
connected screen; `pragma ComponentBehavior: Bound` + `required property var
|
||||||
|
modelData` is the standard pattern for those delegates. A surface that belongs
|
||||||
|
to ONE screen instead takes it as a property from the shell. Note
|
||||||
|
`Quickshell.screens` is a QML list, not a JS array — no `.find()` or `.filter()`
|
||||||
|
on it, hence the index loops in `HyprChromeShell`.
|
||||||
|
|
||||||
**Directory layout**:
|
**Directory layout**:
|
||||||
- `widgets/bar/` — `Bar.qml` is the main sidebar (right-anchored, full height) hosting the module stack (date, clock, tray, decorative dividers); `BarTop.qml`/`BarBottom.qml` are thin accent-colored strips anchored to the top/bottom edges.
|
- `HyprChrome/` — the current shell. `Widgets/HyprChromeShell.qml` is the owner
|
||||||
- `widgets/bar/modules/` — individual bar widgets (`Clock`, `Date`, `Tray`/`TrayItem`, `Volume`) built on the shared `BarWidget` base component.
|
described above; `Widgets/ChromeBackdrop.qml` is the scrim (dim + drafting
|
||||||
- `widgets/launcher/` — shared `AppModel` search/execution plus eight launcher variants. `ApplicationLauncher` (variant 8 and the primary `SUPER` launcher) keeps its visual core in the headlessly renderable `ApplicationLauncherContent`; variants 1–7 remain available on `SUPER CTRL 1–7` for comparison.
|
grid) shared by the rail and the polkit prompt; `Widgets/Bar/` holds the rail
|
||||||
|
and its panels, with `Bar/Panels/BarPanel.qml` the chamfered chrome they all
|
||||||
|
extend; `Widgets/Polkit/` is the authentication agent and its dialog;
|
||||||
|
`Theme/Theme.qml` is this tree's palette singleton. `DebugWindow.qml` stages a
|
||||||
|
single widget on the secondary monitor for eyeballing it in isolation.
|
||||||
|
- `widgets/bar/` — `DenseBar` and `StatusBarPanel`, the rail's predecessor. Not
|
||||||
|
instantiated by `shell.qml` any more; `StatusBarPanel` is still used by the
|
||||||
|
launchers.
|
||||||
|
- `widgets/launcher/` — shared `AppModel` search/execution plus eleven launcher
|
||||||
|
variants. `ApplicationLauncher` (variant 8, and the primary `SUPER` launcher)
|
||||||
|
keeps its visual core in the headlessly renderable
|
||||||
|
`ApplicationLauncherContent`; the rest are available on `SUPER CTRL 1–11` for
|
||||||
|
comparison.
|
||||||
- `widgets/decoration/` — reusable QtQuick `Shape`-based visual accents (angled panel edges, slashes) used to give bar panels their non-rectangular look. `Dummy.qml` is a placeholder/test rectangle.
|
- `widgets/decoration/` — reusable QtQuick `Shape`-based visual accents (angled panel edges, slashes) used to give bar panels their non-rectangular look. `Dummy.qml` is a placeholder/test rectangle.
|
||||||
- `widgets/input/` — thin wrappers around `QtQuick.Controls` inputs (currently just `TextField`).
|
- `widgets/input/` — thin wrappers around `QtQuick.Controls` inputs (currently just `TextField`).
|
||||||
- `widgets/layout/` — `HorizontalStack`/`VerticalStack`: `RowLayout`/`ColumnLayout` wrappers that expose `default property alias content` for terser call sites, with a trailing filler `Item` that soaks up remaining space.
|
- `widgets/layout/` — `HorizontalStack`/`VerticalStack`: `RowLayout`/`ColumnLayout` wrappers that expose `default property alias content` for terser call sites, with a trailing filler `Item` that soaks up remaining space.
|
||||||
- `assets/` — SVG icons referenced via `file://${Quickshell.shellDir}/assets/...`.
|
- `assets/` — SVG icons referenced via `file://${Quickshell.shellDir}/assets/...`.
|
||||||
|
|
||||||
**Styling**: All colors and font families come from the `Theme` singleton
|
**Styling**: All colors and font families come from a `Theme` singleton — there
|
||||||
(`widgets/theme/Theme.qml`, `import qs.widgets.theme`) — there are no color or
|
are no color or font literals left anywhere under `widgets/`. There are TWO,
|
||||||
font literals left anywhere under `widgets/`. Add a token there rather than
|
carrying the same palette for the two trees: `widgets/theme/Theme.qml`
|
||||||
|
(`import qs.widgets.theme`) and `HyprChrome/Theme/Theme.qml`
|
||||||
|
(`import qs.HyprChrome.Theme`). Match the one your file's tree already uses; a
|
||||||
|
token added to one does not exist in the other. Add a token rather than
|
||||||
hardcoding a value; alpha variants of the two main colors go through
|
hardcoding a value; alpha variants of the two main colors go through
|
||||||
`Theme.textAlpha(a)` / `Theme.accentAlpha(a)` instead of a hand-written
|
`Theme.textAlpha(a)` / `Theme.accentAlpha(a)` instead of a hand-written
|
||||||
`Qt.rgba(...)`. Metrics (sizes, spacing) are still per-component.
|
`Qt.rgba(...)`. Metrics (sizes, spacing) are still per-component.
|
||||||
|
|||||||
Reference in New Issue
Block a user