docs(phase-3): full Lua API spec + D23/D24 decisions
docs/lua-api.md (new, 380 lines):
- Section 1-2: Why Lua + file location (owlry.lua, NOT init.lua per D23)
- Section 3: Quick reference (one self-contained example covering every
surface)
- Section 4: API reference (owlry.set / providers / tabs / provider /
theme) with per-field tables and rules
- Section 5: Host API in scope (full stdlib + owlry.util convenience
helpers); no sandbox in 2.1
- Section 6: How providers/tabs/provider{} compose at runtime — the
three orthogonal axes (compiled in / enabled / shown as tab) made
explicit with a worked example and a what-if table
- Section 7: Hot reload via notify crate (re-added in Phase 3)
- Section 8: Validation via 'owlry config validate' / 'config show'
- Section 9: Migration via 'owlry migrate-config' with full TOML→Lua
mapping table
- Section 10: Open questions resolved before Phase 3 ships
- Section 11: Version compatibility roadmap (2.0 -> 2.1 -> 2.2 -> 3.0)
- Section 12: Implementation outline (handoff to engineering)
docs/RESTRUCTURE-V2.md:
- D23: config file named owlry.lua (brand identity over init.lua
convention; file is loaded explicitly, not via Lua's require)
- D24: owlry.providers vs owlry.tabs distinction made explicit — three
orthogonal axes (compiled in / enabled / shown as tab), full
composition spec lives in lua-api.md §6
The Lua API doc is intended as both the design spec we're committing to
AND the user-facing reference once Phase 3 ships.
This commit is contained in:
+501
@@ -0,0 +1,501 @@
|
||||
# Owlry Lua API
|
||||
|
||||
The owlry Lua configuration system. Lands in **2.1** as an opt-in preview alongside TOML; becomes the canonical config format in **3.0** when TOML support is removed.
|
||||
|
||||
> **Status:** design spec for Phase 3 of the v2 rewrite plan. Becomes the user-facing reference doc once Phase 3 ships.
|
||||
|
||||
---
|
||||
|
||||
## 1. Why Lua
|
||||
|
||||
Owlry's 2.0 config is `~/.config/owlry/config.toml`. TOML is fine for flat key/value settings, but it can't express the things people actually want from a launcher config:
|
||||
|
||||
- "Use a different SSH command for `:ssh host-foo` than for everything else"
|
||||
- "Add a Hyprland shutdown menu as a `:hs` provider"
|
||||
- "Build the bookmarks list from `~/Documents/links.md` instead of Firefox"
|
||||
- "Conditionally enable systemd-units only on machines where `$HOSTNAME` matches a regex"
|
||||
|
||||
These are all one-liners in a real programming language. They're impossible in TOML.
|
||||
|
||||
Owlry 2.1 ships a Lua-driven config: the file is real Lua 5.4, evaluated at startup, and the same file holds:
|
||||
|
||||
1. **Global settings** (theme, dimensions, terminal command)
|
||||
2. **Enabled providers** (which built-ins run)
|
||||
3. **Tab layout** (which providers appear as tab buttons)
|
||||
4. **User-defined providers** (custom search sources)
|
||||
5. **Theme overrides**
|
||||
|
||||
This is the same model Hyprland adopted (hyprlang) and that AwesomeWM / wezterm / xmonad have always used: **the config language IS the extension language**. No second-class scripting bolted on alongside a static config.
|
||||
|
||||
---
|
||||
|
||||
## 2. File location & loading order
|
||||
|
||||
The config file is **`~/.config/owlry/owlry.lua`**.
|
||||
|
||||
> Not `init.lua` — the name `owlry.lua` is intentional brand identity. `init.lua` is Lua's `require` entry-point convention; this file isn't loaded by `require`, it's loaded explicitly by owlry, so the convention doesn't apply.
|
||||
|
||||
### Resolution
|
||||
|
||||
On startup the daemon resolves config in this order:
|
||||
|
||||
1. **`$XDG_CONFIG_HOME/owlry/owlry.lua`** (default: `~/.config/owlry/owlry.lua`) — if this file exists, it's the **only** source of truth. TOML is ignored entirely when Lua is present.
|
||||
2. **`$XDG_CONFIG_HOME/owlry/config.toml`** — fallback for 2.x users who haven't migrated. Behavior unchanged from 2.0.
|
||||
3. **Shipped defaults** — `/usr/share/owlry/default.lua` (a stub that calls `owlry.set {}` with reasonable defaults). Used when neither user file exists.
|
||||
|
||||
In **3.0**, step 2 is removed. Users who haven't migrated get a clear error from `owlry config validate` and a pointer to `owlry migrate-config`.
|
||||
|
||||
### Why Lua "wins" over TOML when both exist
|
||||
|
||||
To avoid the "did you remember to delete `config.toml`?" footgun. If you ever write an `owlry.lua`, that's your config — the TOML next to it doesn't quietly partial-override anything. Clear winner: predictable behavior.
|
||||
|
||||
---
|
||||
|
||||
## 3. Quick reference
|
||||
|
||||
```lua
|
||||
-- ~/.config/owlry/owlry.lua
|
||||
local owlry = require("owlry")
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Global settings
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
owlry.set {
|
||||
theme = "owl",
|
||||
width = 850,
|
||||
height = 650,
|
||||
font_size = 14,
|
||||
terminal = "kitty",
|
||||
use_uwsm = false,
|
||||
show_icons = true,
|
||||
max_results = 100,
|
||||
}
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Which providers run (the "enabled set")
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
owlry.providers {
|
||||
"app", "cmd", "calc", "conv", "power", -- always-on built-ins
|
||||
"systemd", "ssh", "websearch", "filesearch", "emoji", "clipboard",
|
||||
}
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Which providers get a tab button in the UI header bar (Ctrl+1..N)
|
||||
-- Must be a subset of the enabled set. If omitted, defaults to all providers.
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
owlry.tabs { "app", "cmd", "uuctl" }
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- A user-defined provider — automatically joins the enabled set.
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
owlry.provider {
|
||||
id = "hs",
|
||||
prefix = ":hs",
|
||||
tab_label = "Shutdown",
|
||||
icon = "system-shutdown",
|
||||
items = function()
|
||||
return {
|
||||
{ name = "Lock", command = "hyprlock" },
|
||||
{ name = "Shutdown", command = "systemctl poweroff" },
|
||||
{ name = "Reboot", command = "systemctl reboot" },
|
||||
}
|
||||
end,
|
||||
}
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Theme selection (or inline definition; see §4.5)
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
owlry.theme("catppuccin-mocha")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. API reference
|
||||
|
||||
The `owlry` module is the only thing in scope. `require("owlry")` returns it. All functions are callable in any order, multiple times — last-write-wins on most settings; some accumulate (see each function's note).
|
||||
|
||||
### 4.1 `owlry.set { ... }` — global settings
|
||||
|
||||
Sets top-level config values. Takes a table of key-value pairs. Calling `owlry.set` multiple times **merges** — later calls override earlier values for the same key. Keys not mentioned keep their default.
|
||||
|
||||
| Key | Type | Default | What it does |
|
||||
|---|---|---|---|
|
||||
| `theme` | `string` | `nil` (system theme) | Theme name (see §4.5 for options) |
|
||||
| `width` | `integer` | `850` | Launcher window width in pixels |
|
||||
| `height` | `integer` | `650` | Launcher window height in pixels |
|
||||
| `font_size` | `integer` | `14` | Base font size in points |
|
||||
| `border_radius` | `integer` | `12` | Window corner radius in pixels |
|
||||
| `terminal` | `string \| nil` | autodetected | Terminal command for items marked `terminal=true`. Falls back to `$TERMINAL` → `xdg-terminal-exec` → common terminals |
|
||||
| `use_uwsm` | `boolean` | `false` | Launch apps via `uwsm app --` for systemd session integration |
|
||||
| `show_icons` | `boolean` | `true` | Show provider icons in results |
|
||||
| `max_results` | `integer` | `100` | Cap on results returned per query |
|
||||
| `frecency` | `boolean` | `true` | Boost frequently/recently used items |
|
||||
| `frecency_weight` | `number` | `0.3` | Frecency boost weight (0.0 = off, 1.0 = strong) |
|
||||
| `search_engine` | `string` | `"duckduckgo"` | Engine for `:web` / `?` queries (see §6) |
|
||||
|
||||
**Example:**
|
||||
|
||||
```lua
|
||||
owlry.set { theme = "owl", width = 900 }
|
||||
owlry.set { font_size = 16 } -- merged: theme + width still applied
|
||||
```
|
||||
|
||||
Unknown keys produce a `owlry config validate` warning, not an error — forward-compat for 2.2+ keys.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 `owlry.providers { ... }` — enabled set
|
||||
|
||||
Lists **which providers run**. Takes a sequence (array) of provider IDs. **Order doesn't matter.**
|
||||
|
||||
```lua
|
||||
owlry.providers { "app", "cmd", "calc", "conv", "power" }
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
- A provider must be in this list to produce any results. If it's not enabled, neither typing its prefix (`:foo`) nor including it in `owlry.tabs` will make it active.
|
||||
- Built-in IDs are: `app`, `cmd`, `dmenu`, `calc`, `conv`, `power`, `systemd` (alias: `uuctl`), `ssh`, `clipboard`, `emoji`, `websearch`, `filesearch`. Pre-2.0 aliases (`sys`, `system` → `power`; `uuctl` → `systemd`) still work.
|
||||
- User providers defined via `owlry.provider {}` **auto-join the enabled set** — you don't list them here unless you want to.
|
||||
- Calling `owlry.providers` multiple times **replaces** the list (it's not additive). Use one call.
|
||||
- If a feature isn't compiled into the binary (e.g. someone built with `--no-default-features`), the provider is silently ignored at runtime — `owlry doctor` reports the mismatch.
|
||||
|
||||
**Default if omitted:** all compiled-in providers are enabled. (The AUR build has `--features full`, so this defaults to everything.)
|
||||
|
||||
---
|
||||
|
||||
### 4.3 `owlry.tabs { ... }` — UI tab buttons
|
||||
|
||||
Lists which providers appear as **tab buttons** in the header bar. Tabs cycle with `Tab` / `Shift+Tab` and can be jumped to with `Ctrl+1..9`.
|
||||
|
||||
```lua
|
||||
owlry.tabs { "app", "cmd", "uuctl" }
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
- Each entry must be in `owlry.providers` (or be a `owlry.provider {}` user provider). Listing an unknown ID produces a `owlry config validate` warning and is silently dropped at runtime.
|
||||
- Order is preserved — `Ctrl+1` targets the first entry, `Ctrl+2` the second, etc.
|
||||
- The implicit "All" tab is always present at position 0 (`Ctrl+0`).
|
||||
- **Default if omitted:** all enabled providers get tabs in the order they were registered.
|
||||
- **`owlry.tabs {}` (empty)** is valid: hides all tab buttons. The "All" tab is still there.
|
||||
|
||||
**The `tabs` ⊆ `providers` rule, made obvious:**
|
||||
|
||||
```
|
||||
provider IS enabled ┌──────────────────────────────────────────┐
|
||||
(runs, contributes results) │ app cmd power calc conv systemd ssh emoji│ <- owlry.providers
|
||||
└─────┬─────┬───────────────┬──────────────┘
|
||||
│ │ │
|
||||
┌─────▼─────▼───────────────▼──────────────┐
|
||||
│ app cmd uuctl │ <- owlry.tabs
|
||||
│ ↑1 ↑2 ↑3 │ (subset; Ctrl+N order)
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
A provider in `providers` but not in `tabs` is fully searchable (auto mode + `:prefix` works), it just doesn't get a permanent tab button. This is the right place for calc/conv/websearch/filesearch — they're triggered with `=` / `>` / `?` / `/` and don't need to take up tab-bar real estate.
|
||||
|
||||
---
|
||||
|
||||
### 4.4 `owlry.provider { ... }` — user-defined provider
|
||||
|
||||
Defines a custom provider that runs alongside the built-ins. The minimum useful shape:
|
||||
|
||||
```lua
|
||||
owlry.provider {
|
||||
id = "hs", -- type_id, used internally
|
||||
prefix = ":hs", -- search prefix (optional)
|
||||
items = function(query) -- called to populate results
|
||||
return {
|
||||
{ name = "Lock", command = "hyprlock" },
|
||||
{ name = "Shutdown", command = "systemctl poweroff" },
|
||||
}
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
**Full field reference:**
|
||||
|
||||
| Field | Type | Required | Default | What it does |
|
||||
|---|---|---|---|---|
|
||||
| `id` | `string` | yes | — | Unique provider ID. Used for filter matching (`-m <id>`) and tab listings. Must be lowercase, alphanumeric + `-`/`_`. |
|
||||
| `items` | `function(query: string) -> table` | yes | — | Returns the list of items. Called on every query if `dynamic = true`, otherwise cached after the first call (see `refresh` below). |
|
||||
| `prefix` | `string \| nil` | no | `nil` | Search prefix (e.g. `":hs "`). Typing the prefix narrows results to this provider. |
|
||||
| `name` | `string` | no | `id` capitalised | Display name shown in `owlry providers`. |
|
||||
| `tab_label` | `string` | no | `name` | Tab button text. |
|
||||
| `icon` | `string` | no | `"application-x-addon"` | XDG icon name shown next to items. |
|
||||
| `search_noun` | `string` | no | `id` | Search placeholder noun (e.g. "Search SSH hosts…"). |
|
||||
| `dynamic` | `boolean` | no | `false` | If true, `items` is called on every keystroke. If false, `items` is called once at startup and cached. **2.1 ships only `false` (static); `true` arrives in 2.2.** |
|
||||
| `priority` | `integer` | no | `0` | Tiebreaker for ordering when multiple providers match. Higher = first. |
|
||||
|
||||
**The item table:**
|
||||
|
||||
Each entry in the table returned by `items` is itself a table:
|
||||
|
||||
| Field | Type | Required | What it does |
|
||||
|---|---|---|---|
|
||||
| `name` | `string` | yes | Item title shown in the result row |
|
||||
| `command` | `string` | yes | Shell command executed on launch |
|
||||
| `description` | `string` | no | Secondary text below the title |
|
||||
| `icon` | `string` | no | Per-item icon (overrides provider icon) |
|
||||
| `terminal` | `boolean` | no | If true, launches inside a terminal |
|
||||
| `tags` | `table` | no | List of tag strings for `:tag:X` filtering |
|
||||
|
||||
**Multiple `owlry.provider` calls accumulate** — each call registers an additional provider. Calling twice with the same `id` overrides the previous registration (warning emitted).
|
||||
|
||||
**Lifecycle:** the provider's `items` function is called by the daemon. It runs in the Lua state with the full host API available (see §5). It must return within a reasonable time — slow `items` functions block the UI. If you need to do work that takes >100ms, cache it externally and refresh on demand (see `dynamic = true` for the 2.2 path).
|
||||
|
||||
---
|
||||
|
||||
### 4.5 `owlry.theme(...)` — theme selection
|
||||
|
||||
Two forms:
|
||||
|
||||
```lua
|
||||
-- Built-in or user theme by name
|
||||
owlry.theme("catppuccin-mocha")
|
||||
```
|
||||
|
||||
Looks up the theme in order: `~/.config/owlry/themes/{name}.css` → bundled theme. Built-in themes: `owl`, `catppuccin-mocha`, `nord`, `rose-pine`, `dracula`, `gruvbox-dark`, `tokyo-night`, `solarized-dark`, `one-dark`, `apex-neon`.
|
||||
|
||||
```lua
|
||||
-- Inline color overrides (merges on top of selected theme or system defaults)
|
||||
owlry.theme {
|
||||
background = "#1e1e2e",
|
||||
background_secondary = "#313244",
|
||||
border = "#45475a",
|
||||
text = "#cdd6f4",
|
||||
text_secondary = "#a6adc8",
|
||||
accent = "#cba6f7",
|
||||
accent_bright = "#f5c2e7",
|
||||
-- Per-provider badge colors (optional)
|
||||
badge_app = "#a6e3a1",
|
||||
badge_cmd = "#fab387",
|
||||
badge_power = "#f38ba8",
|
||||
badge_uuctl = "#9ece6a",
|
||||
}
|
||||
```
|
||||
|
||||
The two forms can be combined — call `owlry.theme(name)` first, then `owlry.theme { ... }` to layer overrides on top.
|
||||
|
||||
---
|
||||
|
||||
## 5. The host API in Lua scope
|
||||
|
||||
User code runs in a Lua 5.4 state with the `mlua` runtime. Beyond the standard library, owlry exposes a small **host API** for things provider code is likely to need.
|
||||
|
||||
### 5.1 What's available in `owlry.lua`
|
||||
|
||||
- **Full Lua 5.4 stdlib** — `math`, `string`, `table`, `os`, `io`, `coroutine`, `package`. No sandboxing. It's your config file on your machine; deal with it.
|
||||
- **The `owlry` module** — `set`, `providers`, `tabs`, `provider`, `theme`. (Documented above.)
|
||||
- **Convenience helpers under `owlry.util`** (added incrementally; ship in 2.1):
|
||||
- `owlry.util.shell(cmd) -> string` — run a shell command, return stdout. Blocking; use sparingly.
|
||||
- `owlry.util.shell_lines(cmd) -> table` — same, split into a list of lines.
|
||||
- `owlry.util.read_file(path) -> string|nil` — read a file's contents, nil if missing.
|
||||
- `owlry.util.glob(pattern) -> table` — list paths matching a glob.
|
||||
- `owlry.util.env(name, default?) -> string` — read an env var with a fallback.
|
||||
- `owlry.util.hostname() -> string` — current hostname.
|
||||
|
||||
### 5.2 What's NOT in scope
|
||||
|
||||
- **No process spawning at config-load time for non-trivial setup.** Provider `items` functions can shell out via `owlry.util.shell`, but the top-level config eval should be fast. If the daemon spends >500ms running `owlry.lua` at startup, something's wrong.
|
||||
- **No network helpers in 2.1.** If a user provider needs HTTP, they can `os.execute("curl ...")` for now. A first-class `owlry.util.http_get` lands in 2.2.
|
||||
- **No reactive state.** Each provider's `items` function should be stateless (or use local upvalues for caching). Cross-call state via shared mutable globals will work but isn't a supported pattern.
|
||||
|
||||
### 5.3 Sandbox stance
|
||||
|
||||
**None in 2.1.** This is the user's config file on the user's machine, by analogy to `bashrc` or `init.vim`. If a future "share your config" community emerges, sandbox concerns kick in then. For now: assume the user wrote the file they're running.
|
||||
|
||||
---
|
||||
|
||||
## 6. How `providers` + `tabs` + `provider {}` compose at runtime
|
||||
|
||||
The single most-asked question this section pre-empts.
|
||||
|
||||
### 6.1 The three axes
|
||||
|
||||
1. **Compiled in** — cargo features at build time. AUR build = all of them. Users on `cargo install --no-default-features` only have a minimal set. Cannot be enabled at runtime.
|
||||
2. **Enabled** — `owlry.providers { ... }` decides which compiled-in providers actually run. Plus all `owlry.provider {}` user definitions are auto-enabled.
|
||||
3. **Shown as tab** — `owlry.tabs { ... }` decides which enabled providers get a button in the header bar.
|
||||
|
||||
A provider must be **compiled in** AND **enabled** to do anything. Being shown as a tab is purely a UI convenience.
|
||||
|
||||
### 6.2 Selection at use time
|
||||
|
||||
On top of the three axes, the user picks at launch time:
|
||||
|
||||
```bash
|
||||
owlry # auto mode — every enabled provider contributes, tabs cycle through
|
||||
owlry -m auto # explicit form of above
|
||||
owlry -m app # single-mode — only `app` runs, even though others are enabled
|
||||
owlry --profile dev # profile (see §6.5) — pre-baked subset of enabled providers
|
||||
```
|
||||
|
||||
Plus the **prefix override** inside the UI: typing `:uuctl foo` narrows the current query to the systemd provider regardless of the mode.
|
||||
|
||||
### 6.3 Worked example
|
||||
|
||||
Given this config:
|
||||
|
||||
```lua
|
||||
owlry.providers { "app", "cmd", "calc", "conv", "power", "systemd", "websearch" }
|
||||
owlry.tabs { "app", "cmd", "systemd" }
|
||||
owlry.provider { id = "hs", prefix = ":hs", items = function() ... end }
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
| Invocation / typed prefix | What runs | Tabs shown |
|
||||
|---|---|---|
|
||||
| `owlry` (no flags), empty query | every enabled provider scored by frecency. `app`, `cmd`, `calc`, `conv`, `power`, `systemd`, `websearch`, `hs` all contribute | `app`, `cmd`, `systemd`, `hs` (`hs` auto-added because it wasn't in `owlry.tabs` → defaults policy kicks in for user providers; see note below) |
|
||||
| `owlry` then user types `:hs ` | only the `hs` provider | tabs unchanged |
|
||||
| `owlry -m app` | only `app` | only the `app` tab is highlighted |
|
||||
| `owlry`, user types `= 2+3` | calculator catches the `=` trigger, returns `5` | tabs unchanged |
|
||||
| `owlry`, user types `:foo bar` | nothing (no provider with id `foo`) | tabs unchanged |
|
||||
| `owlry`, user types `:bookmarks rust` | nothing (`bookmarks` not in `providers` → not enabled, even though it's compiled in) | warning surfaces in `owlry doctor` |
|
||||
|
||||
> **Default tab policy when `owlry.tabs` is omitted:** all enabled providers get tabs.
|
||||
> **When `owlry.tabs` IS present:** user providers from `owlry.provider {}` are NOT auto-added — the user explicitly chose their tab list. To pin a user provider, include its `id` in `owlry.tabs`. (Open: do we want auto-add behavior here? See §10.)
|
||||
|
||||
### 6.4 What happens if a config refers to something missing
|
||||
|
||||
| Situation | Behavior | Where surfaced |
|
||||
|---|---|---|
|
||||
| `owlry.providers` lists a provider not compiled in | Silently dropped at runtime; warning logged | `owlry doctor` lists it under "Not compiled in" |
|
||||
| `owlry.providers` lists an unknown id | Warning at config-validate time | `owlry config validate` prints the offending line |
|
||||
| `owlry.tabs` lists an id not in `owlry.providers` | Warning, dropped from tab bar | `owlry config validate` |
|
||||
| `owlry.provider { items = function() ... end }` errors | Provider returns 0 items for that query; full traceback in daemon log | `owlry doctor` shows the provider as "errored" with the last exception |
|
||||
| Two `owlry.provider {}` calls with same `id` | Second one wins; warning emitted | `owlry config validate` |
|
||||
|
||||
### 6.5 Profiles
|
||||
|
||||
Pre-baked alternate enabled sets (a la 2.0's `--profile`). Inline in Lua:
|
||||
|
||||
```lua
|
||||
owlry.profiles {
|
||||
dev = { "app", "cmd", "ssh" },
|
||||
media = { "emoji", "clipboard" },
|
||||
minimal = { "app" },
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
owlry --profile dev # uses { app, cmd, ssh } instead of owlry.providers's full set
|
||||
```
|
||||
|
||||
Profiles override `owlry.providers` for the launch but inherit `owlry.set` / `owlry.theme` / `owlry.tabs`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Hot reload
|
||||
|
||||
The daemon watches `owlry.lua` and any files it `require`s. On save:
|
||||
|
||||
1. Re-evaluate the Lua state in an isolated context.
|
||||
2. If eval succeeds → swap the new config into the running daemon. Providers reload. No window flicker; no socket disconnect.
|
||||
3. If eval fails → keep the old config alive. The error is logged to the daemon journal and surfaced on the next `owlry doctor` invocation.
|
||||
|
||||
There is **no `systemctl reload`** required. Edit the file, save, the next query reflects the change.
|
||||
|
||||
Hot reload uses the `notify` filesystem watcher (re-added to deps in Phase 3 — it was removed in v2 demolition).
|
||||
|
||||
---
|
||||
|
||||
## 8. Validation & error reporting
|
||||
|
||||
```bash
|
||||
owlry config validate
|
||||
```
|
||||
|
||||
Runs the Lua file in dry-run mode (no side effects, no daemon swap). Reports:
|
||||
|
||||
- Syntax errors with line numbers.
|
||||
- Unknown keys in `owlry.set`.
|
||||
- Unknown IDs in `owlry.providers` / `owlry.tabs`.
|
||||
- `owlry.tabs` entries not in `owlry.providers`.
|
||||
- Duplicate `id` in `owlry.provider {}` calls.
|
||||
- Providers compiled out (warning, not error).
|
||||
|
||||
Exit code: `0` on clean, `1` on any error, `2` on warnings only (configurable).
|
||||
|
||||
```bash
|
||||
owlry config show
|
||||
```
|
||||
|
||||
Prints the **resolved** config as TOML (yes, TOML — for diffability and consistency with how `owlry config show` works today). Useful for debugging "what does owlry actually see?"
|
||||
|
||||
```bash
|
||||
owlry config show --lua
|
||||
```
|
||||
|
||||
Prints the resolved config as a Lua file (round-trippable). Useful for `owlry migrate-config` diff comparison.
|
||||
|
||||
---
|
||||
|
||||
## 9. Migration from TOML
|
||||
|
||||
```bash
|
||||
owlry migrate-config
|
||||
```
|
||||
|
||||
Reads `~/.config/owlry/config.toml`, writes `~/.config/owlry/owlry.lua` with the equivalent settings. Refuses to overwrite an existing `owlry.lua` unless `--force` is passed.
|
||||
|
||||
**Mapping:**
|
||||
|
||||
| TOML | Lua |
|
||||
|---|---|
|
||||
| `[general] theme = "owl"` | `owlry.set { theme = "owl" }` |
|
||||
| `[general] tabs = ["app", "cmd"]` | `owlry.tabs { "app", "cmd" }` |
|
||||
| `[providers] app = true` | included in `owlry.providers { ... }` |
|
||||
| `[providers] app = false` | excluded from `owlry.providers { ... }` |
|
||||
| `[appearance.colors] badge_app = "..."` | merged into `owlry.theme { badge_app = "..." }` |
|
||||
| `[profiles.dev] modes = [...]` | `owlry.profiles { dev = { ... } }` |
|
||||
|
||||
Edge cases:
|
||||
|
||||
- Pre-v2 aliases (`system` config key, `badge_sys` color) are normalized to v2 names (`power`, `badge_power`) in the emitted Lua.
|
||||
- Comments in the TOML are translated to Lua comments where they survive serialization round-trips. Order is preserved as much as possible.
|
||||
- Unknown TOML keys (e.g. removed config options) are commented out in the output with a `-- DROPPED:` prefix.
|
||||
|
||||
The migrator is **deterministic** — running it twice on the same input produces byte-identical output.
|
||||
|
||||
---
|
||||
|
||||
## 10. Open questions (resolved before Phase 3 ships)
|
||||
|
||||
| Q | Default proposed | Notes |
|
||||
|---|---|---|
|
||||
| Auto-add user providers to `owlry.tabs` when tabs is non-empty? | **No** — explicit tabs list is explicit | Easy to revisit; user feedback after 2.1 will decide |
|
||||
| `owlry.theme` separate vs folded into `owlry.set`? | **Separate** — themes are big enough to deserve their own verb | Matches Hyprland's `general` / `decoration` split |
|
||||
| Profile keybinds (per-key Lua functions)? | **Deferred to 2.2** | Needs careful design re: which thread runs the function |
|
||||
| Error reporting depth: traceback in UI banner? | **No in 2.1** — log to journal, surface via `owlry doctor` | UI banner gets noisy fast |
|
||||
| Allow `owlry.lua` to be a directory of files merged together? | **No** — single file. Users wanting modularity use Lua `require` | |
|
||||
| Multi-file config via `require`? | **Yes, supported transparently** — same hot-reload watcher follows `require`d files | Standard Lua semantics |
|
||||
|
||||
---
|
||||
|
||||
## 11. Version & compatibility roadmap
|
||||
|
||||
| Owlry version | Lua config | TOML config | Notes |
|
||||
|---|---|---|---|
|
||||
| **2.0** (shipped) | not supported | canonical | `owlry migrate-config` exists as a stub |
|
||||
| **2.1** (Phase 3) | opt-in preview | still works | `owlry migrate-config` becomes functional |
|
||||
| **2.2** (Phase 3 polish) | preferred; documented | works but warning emitted on load | dynamic providers, `owlry.bind`, `owlry.util.http_get` |
|
||||
| **3.0** (D18) | canonical | **removed** | `owlry config validate` errors loudly when TOML is found without `owlry.lua` |
|
||||
|
||||
---
|
||||
|
||||
## 12. Implementation outline (engineering, not user-facing)
|
||||
|
||||
Tracked in `docs/RESTRUCTURE-V2.md` Phase 3. High level:
|
||||
|
||||
1. Add `mlua` dep (Lua 5.4, vendored, send, serialize)
|
||||
2. New `crates/owlry/src/lua/` module: runtime, api surface, error types, host API utilities
|
||||
3. `crates/owlry/src/config/loader.rs`: resolve order Lua → TOML → defaults; both paths produce the same `Config` struct
|
||||
4. `LuaProvider` impl `Provider`/`DynamicProvider` from a Lua closure
|
||||
5. `notify` watcher re-added; daemon listens for `owlry.lua` changes, re-evaluates in an isolated state, hot-swaps
|
||||
6. `owlry migrate-config` functional; reads TOML, emits Lua
|
||||
7. Characterization tests for every section of this doc; integration test that loads each example end-to-end
|
||||
8. README + CLAUDE.md + `owlry(1)` updated
|
||||
|
||||
Target release: **2.1.0**.
|
||||
Reference in New Issue
Block a user