atagen

monolith

public
Star0
Watch0
1c1f70798e98be513d3c36d1b338c396be0e8564
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: design/review-notes.md

9c7ef7f4bc24atagen2026-10-17 01:31 UTC1
# Codebase review
9b084abcb9c0atagen2026-10-17 01:31 UTC2
9c7ef7f4bc24atagen2026-10-17 01:31 UTC3
A read of every crate on 2026-10-17, after M5 to M7 and the M6 follow-ups. Findings are grouped by theme and ordered by payback within each group. The proposed order of work is at the end.
9b084abcb9c0atagen2026-10-17 01:31 UTC4
9c7ef7f4bc24atagen2026-10-17 01:31 UTC5
## 1. The monolith crate has no internal boundaries
9b084abcb9c0atagen2026-10-17 01:31 UTC6
9c7ef7f4bc24atagen2026-10-17 01:31 UTC7
- **`State` (`state.rs:55`) has 70 public fields**, and all 22 modules are `impl State`. Nothing stops any function touching anything, and every new protocol adds a field and a `new()` line. The fields fall into a few groups:
9c7ef7f4bc24atagen2026-10-17 01:31 UTC8
  - Inert protocol globals that only their own handler touches. These are the 20 or so `*_state` fields and `_dmabuf_global`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC9
  - The backend: `host`, `drm`, `output_id`, `render_node`, `syncobj`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC10
  - The window model, spread over seven maps (see below).
9c7ef7f4bc24atagen2026-10-17 01:31 UTC11
  - Subsystems with their own state: `lock`, `captures`, `imports`, `xwayland`, `launches`, `ipc`, plus `inhibitors` and `inhibited`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC12
  - Input: `suppressed`, `drag`, `pointer_pos`, `move_inverted`, `layer_focus`, `cursor_status`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC13
  - Redraw flags: `needs_redraw`, `cursor_moved`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC14
- **The backend is two `Option`s plus side fields.** `host: Option<Host>` and `drm: Option<Drm>` both exist. `output_id` only means something nested and `render_node` only on bare metal. `after_dispatch` calls both `nested::render` and `drm::render` every time, and each returns early when its option is `None`. A `Backend` enum, or a small trait for render, present and outputs, would make the split explicit.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC15
- **One window's state lives in seven places:**
9c7ef7f4bc24atagen2026-10-17 01:31 UTC16
  - `windows`
9c7ef7f4bc24atagen2026-10-17 01:31 UTC17
  - `last_kind`
9c7ef7f4bc24atagen2026-10-17 01:31 UTC18
  - `awaiting`
9c7ef7f4bc24atagen2026-10-17 01:31 UTC19
  - `placed_by_rule`
9c7ef7f4bc24atagen2026-10-17 01:31 UTC20
  - `scene::Borders`
9c7ef7f4bc24atagen2026-10-17 01:31 UTC21
  - `toplevels::ListHandles`
9c7ef7f4bc24atagen2026-10-17 01:31 UTC22
  - the animator's tracks, glides and spawns
9b084abcb9c0atagen2026-10-17 01:31 UTC23
9c7ef7f4bc24atagen2026-10-17 01:31 UTC24
  `remove_window` (`state.rs:404`) cleans only some of them. `awaiting` and `last_kind` keep stale ids, and `ListHandles` is cleaned lazily in `publish_toplevels`. A per-window record (`Managed { window, kind, awaiting, by_rule, list_handle }`) would make removal one operation.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC25
- **`apply_layout` (`state.rs:469`, about 110 lines)** does six jobs: it collects the shown windows, detects kind switches for glides, retargets animations, configures clients, maps the space, then syncs focus and idle inhibit. It runs after every command, including `Spawn`, `Quit`, `Reload` and `Close`, through `commands.rs:166`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC26
- **The commit handler (`handlers.rs:63`)** runs eight steps in sequence:
9c7ef7f4bc24atagen2026-10-17 01:31 UTC27
  - the buffer handler
9c7ef7f4bc24atagen2026-10-17 01:31 UTC28
  - early import
9c7ef7f4bc24atagen2026-10-17 01:31 UTC29
  - the window's own commit
9c7ef7f4bc24atagen2026-10-17 01:31 UTC30
  - capture damage
9c7ef7f4bc24atagen2026-10-17 01:31 UTC31
  - first-commit mapping
9c7ef7f4bc24atagen2026-10-17 01:31 UTC32
  - first-buffer detection
9c7ef7f4bc24atagen2026-10-17 01:31 UTC33
  - popups
9c7ef7f4bc24atagen2026-10-17 01:31 UTC34
  - layers
9c7ef7f4bc24atagen2026-10-17 01:31 UTC35
  
9c7ef7f4bc24atagen2026-10-17 01:31 UTC36
  It looks up the window by root surface twice, scanning `windows` and `unmapped` linearly each time. Routing on the surface's role first (toplevel, popup, layer, lock, cursor, X11) would make each path short.
9b084abcb9c0atagen2026-10-17 01:31 UTC37
9c7ef7f4bc24atagen2026-10-17 01:31 UTC38
## 2. Drawing an output exists three times
9b084abcb9c0atagen2026-10-17 01:31 UTC39
9c7ef7f4bc24atagen2026-10-17 01:31 UTC40
`drm::render_surface` (`drm.rs:585`, 140 lines), `nested::render` (`nested/mod.rs:169`, 135 lines) and `capture::output_elements` (`capture.rs:265`) each assemble the cursor plus lock-or-scene. Each destructures `State` field by field to get past the borrow checker. On top of that, `render_surface` handles VRR switching, budget accounting, slow-frame logging, lock confirmation, capture damage and frame callbacks. Frame callbacks are written twice, once in each backend, with the lock and layer variants on top.
9b084abcb9c0atagen2026-10-17 01:31 UTC41
9c7ef7f4bc24atagen2026-10-17 01:31 UTC42
- One `frame_elements(world, output) -> Vec<Draw>` used by all three.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC43
- One `send_frame_callbacks(world, output, elapsed)`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC44
- The backends keep pacing and presentation only.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC45
- `scene::build` takes ten arguments (`scene.rs:45`, `#[expect(clippy::too_many_arguments)]`). That disappears once it takes a borrowed view struct.
9b084abcb9c0atagen2026-10-17 01:31 UTC46
9c7ef7f4bc24atagen2026-10-17 01:31 UTC47
**Correctness smell.** `anim.prune(now)` runs inside each output's render with that output's own `now` (`drm.rs:658`). With two outputs at different rates, the later one can prune state the earlier one is still drawing. It's also why `animating` only re-arms the output that noticed it. Pruning belongs once per dispatch, after every output has drawn.
9b084abcb9c0atagen2026-10-17 01:31 UTC48
9c7ef7f4bc24atagen2026-10-17 01:31 UTC49
## 3. Snapshots for clients are rebuilt three ways, every dispatch
9b084abcb9c0atagen2026-10-17 01:31 UTC50
9c7ef7f4bc24atagen2026-10-17 01:31 UTC51
`ipc::broadcast`, `publish_workspaces` and `publish_toplevels` each derive a view of tags, windows and outputs from `Layout` plus `windows`. They run after every batch of events, and they lock every toplevel's role data for titles even when nothing changed.
9b084abcb9c0atagen2026-10-17 01:31 UTC52
9c7ef7f4bc24atagen2026-10-17 01:31 UTC53
- A single derived model, computed only when a dirty flag is set by commits, layout and focus changes, would feed all three.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC54
- `slotmap::Key::data(&id).as_ffi()` appears in both `ipc.rs:177` and `toplevels.rs:26`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC55
- Tag names are looked up by id with a linear `tags().find` twice in `ipc.rs`. `Layout` lacks a `tag(id) -> &Tag` accessor.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC56
9c7ef7f4bc24atagen2026-10-17 01:31 UTC57
## 4. Duplication
9c7ef7f4bc24atagen2026-10-17 01:31 UTC58
9c7ef7f4bc24atagen2026-10-17 01:31 UTC59
- `CLEAR` is defined identically in `drm.rs:40`, `nested/mod.rs:32` and `capture.rs`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC60
- Closing a window (xdg `send_close` or X11 `close`) is written in both `commands.rs:63` and `toplevels.rs:57`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC61
- Window lookup is a linear scan in four places: `State::window_id`, `x11_window_id`, the key search in `toplevels.rs:42` and `ListHandles::window`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC62
- Point-in-rect is written out inline in `drm.rs:787`, `drm.rs:645`, `capture.rs:270` and `state.rs` `surface_under`. The planner's `Rect` has no `contains`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC63
- Rect helpers are scattered: `inset`, `centred` and `translate` in `layout/lib.rs:979`, `pixels` in `scene.rs:279`, `span` and `with_span` in `tree.rs`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC64
- `Dir`, `Axis` and `ExpandScope` are defined in both `monolith-ipc` and `monolith-layout`, with hand-written mapping functions in `commands.rs:42`. `Modifier` and `Mods` from config are matched against smithay's `ModifiersState` by hand in two places (`commands.rs:29`, `input.rs:182`).
9c7ef7f4bc24atagen2026-10-17 01:31 UTC65
- `protocols/src/workspace.rs` and `toplevel.rs` repeat the same skeleton: a manager list, a snapshot, a per-manager sync, bind-then-sync, and stop/destroyed pruning. If a third such protocol arrives, factor out the skeleton. With two, they're tolerable.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC66
- **Config defaults live in two places:** the `Default` impls in `config/src/lib.rs` and `default.toml`, 111 lines. The only test (`config/lib.rs:415`) checks that the file parses, not that it agrees with `Default`. `Raw` (`lib.rs:264`) mirrors `Config` field by field as well.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC67
9c7ef7f4bc24atagen2026-10-17 01:31 UTC68
## 5. Module placement
9c7ef7f4bc24atagen2026-10-17 01:31 UTC69
9c7ef7f4bc24atagen2026-10-17 01:31 UTC70
- `state.rs` free functions belong elsewhere. `root`, `identity` and `configure` are window helpers. `gaps` and `xkb_config` are config adapters. `now_ns` is a clock.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC71
- `handlers.rs` mixes core compositor, xdg-shell, seat, selection, DnD, syncobj, decorations and workspace actions. The newer protocols each got their own module (`layers`, `lock`, `idle`, `capture`, `toplevels`). The older ones should follow so the file stops being a junk drawer.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC72
- `render-vk/src/renderer.rs` (1113 lines) holds:
9c7ef7f4bc24atagen2026-10-17 01:31 UTC73
  - the smithay trait impls
9c7ef7f4bc24atagen2026-10-17 01:31 UTC74
  - submission and the NVIDIA stall logic
9c7ef7f4bc24atagen2026-10-17 01:31 UTC75
  - the 160-line `submit` that records all ops
9c7ef7f4bc24atagen2026-10-17 01:31 UTC76
  - upload and read-back
9c7ef7f4bc24atagen2026-10-17 01:31 UTC77
  - the shm cache
9c7ef7f4bc24atagen2026-10-17 01:31 UTC78
  - `DmabufImporter`
9c7ef7f4bc24atagen2026-10-17 01:31 UTC79
  - the image barrier helpers (`barrier`, `acquire`, `release` at `renderer.rs:690`)
9c7ef7f4bc24atagen2026-10-17 01:31 UTC80
  
9c7ef7f4bc24atagen2026-10-17 01:31 UTC81
  The barriers belong next to `sync.rs`. Stall handling and the shm cache would be self-contained modules.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC82
- `layout/src/lib.rs` (1008 lines) covers tags, outputs and parking, windows, focus history, scratchpads and arrangement in one `impl`. Scratchpads (`765–853`) and output parking (`225–339`) are the clean cuts.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC83
- `anim.rs` keeps `VulkanTexture` inside `Ghost`, which ties the animation model to the GPU. The ghost's texture could live in the scene, keyed by window, with the animator holding only geometry and timing.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC84
- The DRM frame pacing (`Budget`, `Surface::next_deadline`, `drm.rs:86–163`) and nested's `Host::predict` are pure timing logic stuck inside backend files.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC85
9c7ef7f4bc24atagen2026-10-17 01:31 UTC86
## 6. Code quality
9c7ef7f4bc24atagen2026-10-17 01:31 UTC87
9c7ef7f4bc24atagen2026-10-17 01:31 UTC88
- **render-vk has 84 `unsafe` sites and no `// SAFETY:` comments.** The monolith crate does comment its few. `submit` keeps one `unsafe` block across about 120 lines of recording. Narrower blocks with an invariant stated per helper would make the Vulkan code reviewable.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC89
- **Silent `let _ =`** sits on X11 `configure` and `set_*` (`state.rs`, `xwayland.rs`), `notifier.successful`, `popup.send_configure`, `display.flush_clients` and protocol sends. The X11 ones can hide real failures, so log those at debug.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC90
- `commands::spawn` starts one OS thread per child just to `wait()` on it. A SIGCHLD source on the event loop reaps them all without threads.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC91
- `drm::on_input` maps absolute pointer motion onto the first output only (`drm.rs:751`). That's wrong for tablets and multi-monitor.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC92
- `Budget::ns` allocates and sorts 32 samples on every call, which is several times per frame. It's cheap, but a fixed array or a running quantile would do.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC93
- A SIGKILLed monolith leaves `/tmp/.X*-lock` and its socket behind (`xwayland.rs:31` cleans up only on `Drop`). `claim` recovers stale locks by pid, but a recycled pid wastes a display number.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC94
- Comment density is uneven across modules. Do one pass against the commenting rules.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC95
9c7ef7f4bc24atagen2026-10-17 01:31 UTC96
## 7. Tests: what earns its keep and what's missing
9c7ef7f4bc24atagen2026-10-17 01:31 UTC97
9c7ef7f4bc24atagen2026-10-17 01:31 UTC98
What exists:
9c7ef7f4bc24atagen2026-10-17 01:31 UTC99
- **planner:** 5 named strategic cases plus 3 proptests (random transitions, rewinding, interrupts). High value, low count. The custom `Rng` inside proptest is unusual but fine.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC100
- **layout:** a 2000-case proptest of random commands with `check_geometry`, plus two named tree cases. One invariant added this week found a real bug at once.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC101
- **motion:** five small curve and velocity tests.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC102
- **render-vk:** golden image tests, dmabuf round-trips and a transform test.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC103
- **config:** one parse test.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC104
- **monolith:** none.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC105
9c7ef7f4bc24atagen2026-10-17 01:31 UTC106
Gaps, by value:
9c7ef7f4bc24atagen2026-10-17 01:31 UTC107
1. **Layout invariants are cheap and catch real bugs.** Add these to `check_geometry`:
9c7ef7f4bc24atagen2026-10-17 01:31 UTC108
   - the focused window is visible
9c7ef7f4bc24atagen2026-10-17 01:31 UTC109
   - every visible window's tag is viewed on its output, or the window is sticky or a shown scratchpad
9c7ef7f4bc24atagen2026-10-17 01:31 UTC110
   - no window appears in two trees
9c7ef7f4bc24atagen2026-10-17 01:31 UTC111
   - parked outputs restore their windows
9c7ef7f4bc24atagen2026-10-17 01:31 UTC112
2. **Frame pacing.** `Budget` and `next_deadline` are pure, and they have been the source of most of the visible bugs: speed changes, periodic misses, slack decay. A small test that drives a timing sequence through them (a spike, then recovery, then miss and hold) would pin the behaviour that took several bare-metal rounds to find.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC113
3. **Protocol snapshot diffing** in `workspace.rs` and `toplevel.rs` is fiddly and has a history of hidden-state bugs. Pull the diff into a pure function from (sent, snapshot) to events and test it with the cases that matter: removal, output enter and leave, and a property change with no structural change.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC114
4. **Config:** assert that `default.toml` parses to the same values as `Default`, which closes the drift in section 4.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC115
5. **Nested smoke harness.** Every protocol this week was verified with a throwaway shell script that starts nested monolith with clients and greps logs. One `.nu` harness (or an `xtask`) that runs scenarios (layer-shell plus a client, lock, capture, toplevel list, Xwayland) would make those checks repeatable. It could run in `flake check` against lavapipe later.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC116
9c7ef7f4bc24atagen2026-10-17 01:31 UTC117
## Proposed order
9c7ef7f4bc24atagen2026-10-17 01:31 UTC118
e44abf08e08datagen2026-10-19 07:29 UTC119
Done as of 2026-10-17: 1 (State split, see state-split.md, though the routing of commit by surface role proposed under section 1 never landed), 2 (frame path in frame.rs, pruning once per dispatch), 3 (snapshots gated on Dirty::snapshot, identity cached per window). CLEAR and close-window from item 4 went in along the way. 4 (dedup) and 5 (file moves) are done too. From 5, `now_ns`, `gaps` and `xkb_config` stay in state.rs. The ghost texture moved to `Wm::last_frames`. 6 and 7 are done as well. Two parts were left out on purpose. Snapshot diffing stays untested as a pure function, because its bugs so far were in the publishers and scripts/smoke.nu catches those end to end. The comment pass removed nothing, since density is under 10% outside module docs and existing comments are vetted.
14dab738af1aatagen2026-10-17 03:14 UTC120
14dab738af1aatagen2026-10-17 03:14 UTC121
9c7ef7f4bc24atagen2026-10-17 01:31 UTC122
1. **Split `State`** into protocol globals, the backend enum, the window model with per-window records, subsystems and input. This unlocks most of the rest.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC123
2. **One output frame path:** element assembly and frame callbacks shared by both backends and capture, with pruning once per dispatch.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC124
3. **One derived snapshot with a dirty flag**, feeding ipc, ext-workspace and wlr-toplevel.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC125
4. **Mechanical dedup:** `CLEAR`, close, window index, `Rect::contains`, the shared enums between ipc and layout, the modifier mapping, and single-source config defaults.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC126
5. **File moves:** `handlers.rs` per protocol, the render-vk barriers, stall and shm cache modules, layout scratchpads and parking, frame pacing out of `drm.rs`.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC127
6. **Tests:** layout invariants, pacing, snapshot diffs, config defaults, then the smoke harness.
9c7ef7f4bc24atagen2026-10-17 01:31 UTC128
7. **Hygiene sweep:** SAFETY comments, `let _ =` triage, the child reaper, absolute pointer mapping, and comments.