# Performance notes The plugin is designed so that a closed dropdown costs nothing and an open one costs a known, tunable amount. ## Measured Baseline before the pixel-water redesign. Machine: 24 core, 5K display (5120x2880), Quickshell 0.3.1, Qt 6.11. `top` on the shell process, 3 second samples. | State | Shell CPU | | --- | --- | | Dropdown closed | 0.0 to 0.3% | | Dropdown open, wave at 30 fps | 4.7 to 5.7% of one core | Every frame the shell produces costs roughly the same on this display whatever moved in it, so frames per second is the whole price. `motionHz` is the knob. ## Decisions **Water is one ShaderEffect quad.** `ui/water.frag` evaluates a glass signed distance field, two sine waves and the fill level on a quantized pixel grid, with depth bands and dither. The stepped glass outline uses static rectangles. Moving the wave is a uniform write. There is no Canvas raster, no QtQuick.Shapes re-tessellation, no `layer.enabled` mask pass. The shader is precompiled with `qsb --glsl "100 es,120,150"` and the QML checks `status === ShaderEffect.Compiled`, falling back to a flat Shape fill if the GPU says no. **One throttled Timer, not FrameAnimation, not `Animation.Infinite`.** An infinite animation makes the render loop request a frame every vsync. The Timer runs at `motionHz` only while the dropdown is open and the item is visible. Splash energy decays on the same tick, so there is no second clock. **Level glide is a short Behavior.** It runs 900 ms after a pour and then stops. Bubbles are five short, one shot animations. **Bar glyph is a Canvas that repaints on change only.** No timer. A pour animates the level for 600 ms and the pour lift for 700 ms, then the Canvas is idle until the next drink. **Closed dropdown evaluates nothing.** `week` and `hourly` bindings in the panel are gated on `opened`, so a tick in the service while closed does not rebuild any rows. **Stats are free until opened.** Every stats binding hangs off `statsOpen`, so the month grid, trend and lifetime numbers are computed only while that face is up, and each is one pass over at most 366 keys. The month grid is at most 42 Rectangles. **Storage stays small.** Two tiers in one JSON file: 14 days with drink lists, then totals only, pruned at 366 days. Compaction runs inside every commit as a single walk. **Service cost.** A 30 second Timer runs `reminderDue`, a handful of integer compares. `SystemClock` at minute precision bumps `nowMs` for relative labels. File I/O happens only on a change, through `mktemp` plus atomic rename. ## Rebuilding the shader ```bash /usr/lib/qt6/bin/qsb --glsl "100 es,120,150" -o ui/water.frag.qsb ui/water.frag ``` ## Gotcha Hot reload does not apply code changes on Quickshell 0.3.1: the shell's reload path guards on `Qt.clearComponentCache`, which does not exist there, so the new instances are built from cached components. Symlinked plugin folders never trigger a reload at all (`inotifywait -r` does not follow them). Run `omarchy restart shell` after editing QML. The research that led here is in [research-qml-perf.md](research-qml-perf.md).