From 163e68af9ede8d8a567bf15c92fd9ef3d5367304 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Wed, 13 May 2026 01:36:55 +0200 Subject: [PATCH] docs(v2): lock down restructure plan Comprehensive plan covering all 5 phases of the v2 restructure: decisions log, target shape, CLI layout, feature naming, conversion notes, breaking changes, deferred questions, and acceptance checklist. This document is the source of truth for the v2 work; future sessions read this first to recover context. --- docs/RESTRUCTURE-V2.md | 440 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 440 insertions(+) create mode 100644 docs/RESTRUCTURE-V2.md diff --git a/docs/RESTRUCTURE-V2.md b/docs/RESTRUCTURE-V2.md new file mode 100644 index 0000000..c2c9251 --- /dev/null +++ b/docs/RESTRUCTURE-V2.md @@ -0,0 +1,440 @@ +# Owlry v2 Restructure Plan + +**Status:** in progress on branch `v2` (cut from `main @ 1caa050`) +**Target version:** `owlry 2.0.0` +**Scope:** repository collapse, C-ABI removal, runtime consolidation, CLI cleanup, single AUR package +**Originating issue:** [#5 uuctl launcher not working](https://somegit.dev/Owlibou/owlry/issues/5) — closed by deletion of the C-ABI loader that caused it + +--- + +## 0. Decisions log + +All design decisions agreed before work started. These are load-bearing — do not silently change them. + +| # | Decision | Rationale | +|---|---|---| +| D1 | Collapse to **1 AUR package**: `owlry` | 14 PKGBUILDs maintained in lockstep was the root cost of every API bump. Native plugin distribution was theoretical (no third-party shipped any). | +| D2 | **Drop the C-ABI plugin system entirely** | Issue #5's root cause: strict `api_version != API_VERSION` rejection. Compiling providers in eliminates the failure mode. | +| D3 | **Drop Rune runtime** | Only one user plugin (`hyprshutdown`) uses Rune; will be ported to Lua. 118 MB dylib for one plugin is not earned. | +| D4 | **Use Lua for config + plugins** via `mlua` (Lua 5.4, bundled) — *Phase 3+* | Single language for both configuration and user extension. LuaJIT perf is irrelevant for a config language. | +| D5 | **Configs-as-plugins** model | `owlry.provider {}` in `init.lua` registers a runtime provider treated identically to a built-in. No separate `~/.config/owlry/plugins/` dir. | +| D6 | **Daemon as subcommand** with `-d` short alias: `owlry daemon` or `owlry -d` | Single binary; `owlryd` no longer exists. systemd unit invokes `owlry -d`. | +| D7 | **`-m auto`** as explicit alias for the implicit no-flag default | Today's `accept_all` behavior — all enabled providers active, tabs cycle. Pinned with an integration test so refactors can't regress it. | +| D8 | **Hard cut on TOML config** in a later phase | Migrator command `owlry migrate-config` reads existing TOML + script plugins, emits `init.lua`. One-shot; TOML reader deleted in the same release that ships Lua. | +| D9 | **AUR ships `--features full`** | Defaults in `Cargo.toml` are minimal; AUR PKGBUILD enables everything. End-user picks at runtime via config, not at install time. | +| D10 | **Runtime activation is config-driven** | Compiled-in ≠ enabled. Config (TOML now, Lua later) decides which providers run. | +| D11 | **Drop `config_editor` provider** (1127 LOC) | An in-launcher settings editor is replaced by editing `init.lua` + `owlry config validate`. | +| D12 | **Drop `scripts` provider** | Replaced by `owlry.provider {}` in Lua config (Phase 3+). | +| D13 | **Rename `sys` → `power`** | "sys" collides mentally with "systemd". `:sys` kept as alias. | +| D14 | **Keep submenu protocol** | Used by systemd provider for service actions. Becomes a method on the `Provider` trait. | + +--- + +## 1. Target shape (post-Phase 2 — what 2.0.0 ships as) + +### Repo layout + +``` +owlry/ +├── Cargo.toml -- workspace with single member +├── crates/owlry/ +│ ├── Cargo.toml -- one binary, features per provider +│ └── src/ +│ ├── main.rs -- subcommand router +│ ├── cli.rs -- clap defs +│ ├── server.rs -- daemon mode (was owlry-core/src/server.rs) +│ ├── client.rs -- IPC client to daemon (UI mode) +│ ├── backend.rs -- abstracts daemon vs local +│ ├── ipc.rs -- Request/Response types +│ ├── filter.rs -- ProviderFilter +│ ├── paths.rs -- XDG paths +│ ├── notify.rs -- desktop notifications +│ ├── theme.rs -- CSS loading +│ ├── config/ -- config loader (TOML in Phase 1, Lua in Phase 3) +│ ├── data/ -- FrecencyStore +│ ├── providers/ -- ALL providers, each gated by feature +│ │ ├── mod.rs -- ProviderManager, Provider traits, ProviderType +│ │ ├── application.rs (feature: app) +│ │ ├── command.rs (feature: cmd) +│ │ ├── calculator.rs (feature: calc) +│ │ ├── converter/ (feature: conv) +│ │ ├── power.rs (feature: power, was sys) +│ │ ├── dmenu.rs (feature: dmenu, moved from owlry/src/providers/) +│ │ ├── bookmarks.rs (feature: bookmarks) +│ │ ├── clipboard.rs (feature: clipboard) +│ │ ├── emoji.rs (feature: emoji) +│ │ ├── ssh.rs (feature: ssh) +│ │ ├── systemd.rs (feature: systemd, type_id "uuctl") +│ │ ├── websearch.rs (feature: websearch) +│ │ ├── filesearch.rs (feature: filesearch) +│ │ ├── weather.rs (feature: weather) +│ │ ├── media.rs (feature: media) +│ │ └── pomodoro.rs (feature: pomodoro) +│ └── ui/ -- GTK4 client +├── data/ +│ ├── config.example.toml -- shipped defaults (Phase 1) +│ └── default-config.lua -- shipped defaults (Phase 3+) +├── systemd/ +│ ├── owlryd.service -- renamed to owlry.service in Phase 2; ExecStart=/usr/bin/owlry -d +│ └── owlryd.socket -- renamed to owlry.socket +└── aur/owlry/PKGBUILD -- the only AUR package +``` + +### Deleted entirely + +| Path | Reason | +|---|---| +| `crates/owlry-core/` | Folded into `crates/owlry/` | +| `crates/owlry-plugin-api/` | C-ABI gone | +| `crates/owlry-lua/` | mlua compiled in via Phase 3 | +| `crates/owlry-rune/` | Rune dropped (D3) | +| `crates/owlry-core/src/plugins/` (entire dir) | C-ABI loader, runtime loader, manifest, registry, watcher, error | +| `crates/owlry-core/src/providers/native_provider.rs` | C-ABI bridge | +| `crates/owlry-core/src/providers/lua_provider.rs` | Old Lua bridge | +| `crates/owlry-core/src/providers/config_editor.rs` | D11 | +| `crates/owlry/src/plugin_commands.rs` (1296 LOC) | No plugin CLI | +| `aur/owlry-core/`, `aur/owlry-lua/`, `aur/owlry-rune/` | Folded into `aur/owlry/` | +| `aur/owlry-plugin-*` (none yet in this repo) | n/a | +| `aur/owlry-meta-*` (essentials, widgets, tools, full) | Redundant | +| `owlry-plugins/` sibling repo | Archived on somegit.dev | + +--- + +## 2. CLI shape (post-Phase 1) + +``` +owlry launch UI, auto mode (default) +owlry -m auto launch UI, auto mode (explicit alias) +owlry -m launch UI in single-provider mode +owlry --profile launch UI with a named profile + +owlry daemon run daemon (alias: owlry -d) +owlry dmenu [-p ] dmenu mode (was: owlry -m dmenu) +owlry doctor diagnostics: providers, config, socket, runtime +owlry providers [] list providers (or show one) +owlry config validate check config for errors +owlry config show print resolved effective config +owlry migrate-config TOML → init.lua (Phase 3+) +``` + +### Removed CLI surface + +The entire `owlry plugin ...` subcommand tree (~1296 LOC in `plugin_commands.rs`) is deleted: +`list`, `search`, `info`, `install`, `remove`, `update`, `enable`, `disable`, `create`, `validate`, `runtimes`, `run`, `commands`. +There is no plugin installation to manage — features are compiled in. + +--- + +## 3. Feature names + descriptions + +Final naming for cargo features and corresponding provider IDs: + +| Feature | CLI mode / prefix | What it does | +|---|---|---| +| `app` | `:app` | XDG desktop applications | +| `cmd` | `:cmd` | Executables on `$PATH` | +| `calc` | `:calc` / `=` | Calculator (math expressions, e.g. `= 2+2`) | +| `conv` | `:conv` | Unit & currency converter (e.g. `5 ft to m`) | +| `power` | `:power` (`:sys` alias) | Shutdown, reboot, logout, suspend, lock — was `sys` | +| `dmenu` | (subcommand) | Pipe-based selection — stdin in, selection out | +| `bookmarks` | `:bm` | Browser bookmarks (Firefox, Chromium) | +| `clipboard` | `:clip` | Clipboard history via `cliphist`/`wl-clipboard` | +| `emoji` | `:emoji` | Emoji picker (writes via `wl-clipboard`) | +| `ssh` | `:ssh` | SSH hosts from `~/.ssh/config` | +| `systemd` | `:systemd` (`:uuctl` alias) | systemd user units (type_id stays "uuctl" for config compat) | +| `websearch` | `:web` / `?` | Web search (DDG, Google, custom engines) | +| `filesearch` | `:file` / `/` | File search via `fd` or `mlocate` | +| `weather` | (widget) | Weather widget at top of results | +| `media` | (widget) | MPRIS media player controls | +| `pomodoro` | (widget) | Pomodoro focus timer widget | + +### Cargo feature groups + +```toml +[features] +default = ["app", "cmd", "calc", "conv", "power", "dmenu"] +full = [ + "app", "cmd", "calc", "conv", "power", "dmenu", + "bookmarks", "clipboard", "emoji", "ssh", "systemd", + "websearch", "filesearch", "weather", "media", "pomodoro", +] +dev-logging = [] +``` + +AUR PKGBUILD builds with `--features full`. + +--- + +## 4. Runtime config (Phase 1: TOML, Phase 3+: Lua) + +Three orthogonal axes: + +1. **Compiled in** — Cargo features at build time. AUR ships `full`. +2. **Enabled** — Config decides which providers actually run. +3. **Selected** — `-m` / `--profile` / Tab key narrows the active set at use time. + +### Phase 1 (TOML, kept temporarily) + +```toml +[general] +tabs = ["app", "cmd", "calc", "conv", "power"] # what shows in auto mode + +[providers] +applications = true +commands = true +calculator = true +converter = true +power = true # was `system` +bookmarks = false # opt-in +clipboard = false +# ...etc +``` + +### Phase 3+ (Lua, target) + +```lua +local owlry = require("owlry") + +owlry.set { + theme = "apex-neon", + width = 850, height = 650, + tabs = { "app", "cmd", "power" }, +} + +owlry.providers { "app", "cmd", "calc", "conv", "power", "bookmarks", "systemd" } + +owlry.provider { + id = "hs", prefix = ":hs", tab_label = "Shutdown", + items = function(_q) + return { + { name = "Lock", command = "hyprlock" }, + { name = "Shutdown", command = "systemctl poweroff" }, + { name = "Reboot", command = "systemctl reboot" }, + } + end, +} +``` + +--- + +## 5. Phased plan + +### Phase 1 — Repo collapse (current) + +Goal: single workspace member, single binary, TOML config still works, C-ABI gone, all built-in providers compiled in behind features. + +Subtasks (tracked in TaskList): + +1. ✅ Inventory: map Provider trait + plugin shapes +2. **Workspace skeleton: collapse to single crate** — move `owlry-core/src/*` into `owlry/src/`, absorb deps, remove other crates from workspace +3. **Delete C-ABI plugin system** — `plugins/` dir, `native_provider.rs`, `lua_provider.rs`, `owlry-plugin-api` crate; strip `ProviderManager::new_with_config()` of plugin loading +4. **Delete Rune + Lua runtime crates** — `crates/owlry-rune/`, `crates/owlry-lua/` +5. **Delete config_editor + scripts providers** +6. **Convert 11 plugins to native Provider impls** — pull source from `owlry-plugins/crates/owlry-plugin-*`, convert `extern "C"` vtable → `impl Provider` / `impl DynamicProvider`. Per-plugin mechanical work. +7. **Wire cargo features per provider** — `#[cfg(feature = "...")]` gating; `default` / `full` feature groups +8. **Rename `sys` → `power`** — file, type_id (in CLI mode mapping table), `:sys` kept as alias, config key `providers.system` → `providers.power` (with TOML migration shim that reads the old name) +9. **CLI restructure** — new clap shape (subcommands `daemon`, `dmenu`, `doctor`, `providers`, `config`, `migrate-config`); drop entire `plugin` subcommand tree; daemon mode via `owlry -d` / `owlry daemon` +10. **Auto-mode integration test** — `tests/auto_mode.rs` asserts the no-flag default still surfaces results from all enabled providers and tabs match `general.tabs` +11. **Phase 1 final build + smoke** — `cargo check --workspace`, `cargo build --release`, `cargo test`, runtime socket smoke + +### Phase 2 — AUR republish as 2.0.0 + +Goal: ship the collapsed binary to users. Issue #5 closes by virtue of the C-ABI being gone. + +Steps: + +- Update `aur/owlry/PKGBUILD`: + - `pkgver=2.0.0` + - `replaces=('owlry-core' 'owlry-lua' 'owlry-rune' 'owlry-plugin-bookmarks' 'owlry-plugin-clipboard' 'owlry-plugin-emoji' 'owlry-plugin-filesearch' 'owlry-plugin-media' 'owlry-plugin-pomodoro' 'owlry-plugin-scripts' 'owlry-plugin-ssh' 'owlry-plugin-systemd' 'owlry-plugin-weather' 'owlry-plugin-websearch' 'owlry-meta-essentials' 'owlry-meta-widgets' 'owlry-meta-tools' 'owlry-meta-full')` + - `conflicts=()` + - `provides=()` + - `build()` uses `cargo build --release --features full` + - `package()` installs single binary, single systemd service+socket, default config +- Delete the 14 other PKGBUILDs under `aur/` +- `git tag owlry-v2.0.0` +- Push, build, `aur-publish` +- Archive `owlry-plugins` sibling repo on somegit.dev (read-only, history preserved) +- Comment on issue #5: "fixed by 2.0 rewrite; the C-ABI plugin loader that produced this class of bug has been removed" + +### Phase 3 — Lua config core (later release: 2.1.0 or 3.0.0) + +Goal: replace TOML with Lua-driven config. Hyprland-style configs-as-code. + +Steps: + +- Add `mlua = { version = "0.11", features = ["lua54", "vendored", "send", "serialize"] }` dependency +- Build the `owlry.*` Lua API surface: + - `owlry.set { ... }` — global settings (theme, dimensions, tabs) + - `owlry.providers { ... }` — enable list (subset of compiled-in features) + - `owlry.provider { id, prefix, tab_label, items, ... }` — register a runtime provider + - `owlry.bind(key, action)` — keybinding overrides + - `owlry.theme(name | { ... })` — theme selection or inline definition +- Resolve config: `~/.config/owlry/init.lua` if present; else fall back to shipped `data/default-config.lua` +- `owlry migrate-config` subcommand: read existing `config.toml` (+ `~/.config/owlry/plugins/*` if any) and emit equivalent `init.lua` +- Wire `owlry config validate` and `owlry config show` to surface Lua errors and resolved state +- Decision pending: hard cut TOML in 3.0.0 vs. dual-read in 2.1.0 → cut in 3.0.0 + +### Phase 4 — Configs-as-plugins (same release as Phase 3) + +Goal: Lua-registered providers are first-class. Drop the script plugin discovery dir. + +Steps: + +- `owlry.provider {}` creates a `LuaProvider` that wraps the supplied Lua closures and implements the `Provider` trait +- Remove the `~/.config/owlry/plugins/` filesystem discovery entirely +- The shipped `default-config.lua` showcases at least one inline provider as documentation + +### Phase 5 — Hygiene + +Goal: clean up debt that the collapse uncovered. + +Steps: + +- Update `CLAUDE.md` — binary name (`owlry`, not `owlry-core`/`owlryd`), workspace layout, drop "Phase 5 pending" wording +- Update `README.md` — new CLI, new install instructions, drop AUR package list +- Refresh `ROADMAP.md` +- Split files >1000 LOC: + - `providers/mod.rs` — separate `ProviderType` / `LaunchItem` / `ItemSource` from `ProviderManager` + - `ui/main_window.rs` — input handling, render, signal wiring as separate modules + - `providers/converter/units.rs` — move data tables to a `units.toml` resource +- Fix double-daemon spawn: rely on socket activation only; add `flock` guard in `main.rs` if needed +- Make sure systemd unit name is `owlry.service` (or `owlryd.service`?) — decide before AUR ship + +--- + +## 6. Provider-by-provider conversion notes (Phase 1.6) + +Mechanical pattern for each plugin: drop `extern "C"` exports, `PluginItem` → `LaunchItem`, opaque `ProviderHandle` → struct field on a `Provider` impl. + +### `provider_kind`-driven categorization (current state) + +| Plugin | Kind | Submenu? | Per-keystroke? | Notes | +|---|---|---|---|---| +| bookmarks | Static | no | no | SQLite for Firefox; bundled rusqlite | +| clipboard | Static | yes (?) | no | Uses `cliphist`, `wl-clipboard` | +| emoji | Static | no | no | Bundled emoji data; writes via `wl-clipboard` | +| ssh | Static | no | no | Parses `~/.ssh/config` | +| systemd | Static | **yes** | no | type_id "uuctl"; SUBMENU: protocol for service actions | +| scripts | DELETE | — | — | Replaced by Lua config (D12) | +| websearch | Dynamic | no | yes | per-keystroke; `?` trigger | +| filesearch | Dynamic | no | yes | per-keystroke; `/` trigger; `fd` or `mlocate` | +| weather | Widget | no | no | Top-of-results; network call on refresh | +| media | Widget | yes | no | MPRIS control via submenu actions | +| pomodoro | Widget | yes | no | Start/pause/reset via submenu actions | + +### Submenu mechanism on `Provider` trait + +Add to `pub trait Provider`: + +```rust +/// Returns submenu actions for the given encoded data string, or empty if none. +/// Called when a user selects an item whose command field starts with "SUBMENU:". +fn submenu_actions(&self, _data: &str) -> Vec { Vec::new() } + +/// Handle a plugin-defined action command (e.g. "POMODORO:start"). +/// Returns true if handled. +fn execute_action(&self, _command: &str) -> bool { false } + +/// Optional UI hints — replace today's hardcoded match table. +fn prefix(&self) -> Option<&str> { None } +fn icon(&self) -> Option<&str> { None } +fn position(&self) -> ProviderPosition { ProviderPosition::Normal } +fn priority(&self) -> u32 { 0 } +``` + +`ProviderManager::query_submenu_actions` and `execute_plugin_action` route to these trait methods instead of the C-ABI `NativeProvider`. + +--- + +## 7. Breaking changes / migration impact + +### What users lose at 2.0.0 + +- 14 separate AUR packages → 1 (paru handles via `replaces`/`conflicts`) +- Ability to install a subset of plugins via AUR (still possible via `cargo install --no-default-features --features ...`) +- Rune scripting (the user's `hyprshutdown` plugin breaks; will be ported to Lua in Phase 3, or compiled into a small custom build) +- Dynamic plugin loading at runtime (was never used by anyone outside Owlibou) +- `~/.config/owlry/plugins/` directory (Phase 4 — but already orphaned at 2.0.0 since Rune is gone) + +### What stays unchanged for users + +- Launcher behavior, keybindings, themes, search prefixes +- Mode flags: `-m app`, `-m systemd`, `-m uuctl` (alias), `-m power` (was `-m sys`) +- TOML config compatibility through 2.x — until Phase 3 hard-cut to Lua +- Socket protocol (mostly — `Request::PluginList` is dropped; `Submenu` and `PluginAction` stay) +- systemd integration (unit file invokes `owlry -d` instead of `owlryd`) + +### `replaces=()` array for `aur/owlry/PKGBUILD` (2.0.0) + +``` +owlry-core +owlry-lua +owlry-rune +owlry-plugin-bookmarks +owlry-plugin-clipboard +owlry-plugin-emoji +owlry-plugin-filesearch +owlry-plugin-media +owlry-plugin-pomodoro +owlry-plugin-scripts +owlry-plugin-ssh +owlry-plugin-systemd +owlry-plugin-weather +owlry-plugin-websearch +owlry-meta-essentials +owlry-meta-widgets +owlry-meta-tools +owlry-meta-full +``` + +--- + +## 8. Open questions / decisions deferred to later phases + +| Question | Defer to | Notes | +|---|---|---| +| systemd unit name: `owlry.service` vs keep `owlryd.service`? | Phase 2 | Probably rename for consistency; PKGBUILD handles old name via `backup` field | +| Submenu protocol redesign (`SUBMENU:type:data` string-encoded → typed IPC variant) | Phase 5 | Works today; not blocking | +| Drop daemon entirely (collapse into single-process UI)? | Post-2.0 | Profile first; daemon's perf justification is dubious but not measured | +| TOML reader removal timing — 2.1.0 dual-read or 3.0.0 hard cut? | Phase 3 entry | Hard cut is cleaner; one migration moment | +| LuaProvider sandboxing model — what stdlib subset can user `init.lua` access? | Phase 3 | At minimum: filesystem read, process spawn (it's a launcher), no network by default | +| Where do widgets (`weather`, `media`, `pomodoro`) live in the UI after the rewrite? | Phase 1.6 | Today they're "position: Widget" rendered above results; keep that | +| Hot-reload of `init.lua` on save? | Phase 3 | Nice-to-have; `notify` crate is already a dep | + +--- + +## 9. Build / verify checklist (Phase 1 acceptance) + +Phase 1 is "done" when all of these pass: + +- [ ] `cargo check --workspace` (no warnings on default features) +- [ ] `cargo check --workspace --all-features` (no warnings) +- [ ] `cargo build --release --features full` +- [ ] `cargo test --workspace --all-features` +- [ ] `cargo clippy --workspace --all-features -- -D warnings` +- [ ] `cargo fmt --all --check` +- [ ] Workspace contains only `crates/owlry` member +- [ ] `crates/owlry-core/`, `crates/owlry-plugin-api/`, `crates/owlry-lua/`, `crates/owlry-rune/` no longer exist +- [ ] No occurrences of `owlry_plugin_api`, `mlua`, `libloading`, `Rune`, `liblua.so`, `librune.so` outside historical docs +- [ ] `target/release/owlry` runs: + - `owlry --help` shows new subcommand structure + - `owlry doctor` reports providers + config + socket status + - `owlry -d` starts daemon; socket listens at `$XDG_RUNTIME_DIR/owlry/owlry.sock` + - `owlry -m systemd` shows systemd units (Issue #5 fixed locally) + - `owlry` (no flags) shows auto mode with results from all enabled providers, tabs match `general.tabs` +- [ ] Integration test `tests/auto_mode.rs` passes +- [ ] No daemon double-spawn (verify `pgrep -af owlry` shows one process when launched normally) + +--- + +## 10. Working notes (mutable) + +This section captures in-progress state. Update freely as work proceeds. + +- **Branch:** `v2`, cut from `main @ 1caa050` on 2026-05-13 +- **Current task:** #3 (Delete C-ABI plugin system) — in progress +- **Stray processes from inventory phase:** + - PIDs 3042, 3278 — pre-existing `owlryd` (double-spawn bug; will resolve via Phase 5) + - PID 594897 — test daemon from inventory probe; harness denied kill; resolves at next reboot or user kill +- **Hyprland exec-once for owlryd:** suspected source of double-spawn; verify and remove during Phase 5 +- **`plugin-api-v1.0.1` git tag:** still has `API_VERSION = 3`. Moot once C-ABI is deleted.