1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//! Plugin entrypoint — `define_plugin_install!` and install steps.
//!
//! # Plugin definition (`mod.rs`)
//!
//! Every plugin lives in `src/plugins/<name>/mod.rs` (or `src/plugins/<name>.rs` for small
//! plugins). The file declares:
//!
//! 1. A **tag struct** (e.g. `HelloTag`) — identifies the plugin in the capability system.
//! 2. Submodule declarations (`routes`, `templates`, `handlers`, …).
//! 3. An **`install` function** generated by [`define_plugin_install!`](crate::plugin_install::define_plugin_install).
//!
//! ```ignore
//! pub mod routes;
//! pub mod templates;
//! pub mod handlers;
//!
//! pub struct MyPluginTag;
//!
//! define_plugin_install! {
//! plugin: MyPluginTag;
//! /// Doc comment attached to the generated `install` function.
//! steps: [
//! apps(apps::Hook),
//! migrations(migrations::Hook),
//! templates(templates::Hook),
//! slots(templates::SlotsHook),
//! config(MyConfigTag, MyConfig),
//! http(routes::Hook),
//! state(StateHook),
//! seeds(SeedsHook),
//! commands(cli::Hook),
//! ]
//! }
//! ```
//!
//! # Install steps
//!
//! Each step prepends a deferred hook onto a core capability. At mount time hooks fold
//! in reverse install order (last plugin wins for conflicting registrations).
//!
//! | Step | Registers |
//! |------|-----------|
//! | `cap_attach($Tag, $Cap, $expr)` | Prepend a custom builder capability (tag must be absent) |
//! | `cap_hook($Tag, $Cap, $hook)` | Prepend a hook on an existing tagged capability |
//! | `apps($hook)` | Dashboard app tile |
//! | `migrations($hook)` | SeaORM migrator |
//! | `templates($hook)` | Maud page types |
//! | `slots($hook)` | Shell chrome slots (topbar, head, …) |
//! | `config($Tag, $Ty)` | TOML config section with defaults |
//! | `http($hook)` | HTTP routes |
//! | `state($hook)` | Runtime state after DB connect |
//! | `seeds($hook)` | Startup seed data |
//! | `commands($hook)` | CLI subcommands |
//! | `grapesjs($hook)` | Website builder blocks |
//! | `tools($hook)` | LLM function-calling tools |
//! | `export($hook)` | XLSX export catalog tables |
//! | `rune_env($hook)` | Rune script bindings |
//!
//! # Custom capabilities (`cap_attach` / `cap_hook`)
//!
//! Core Lariv capabilities (templates, apps, HTTP, …) are always present on a web app.
//! Deployments can add **local builder capabilities** — a struct in your crate that
//! implements [`Capability`](crate::capability::Capability) and [`HasCapTag`](crate::capability::HasCapTag),
//! with plugin hooks prepended via [`CapHookExt`](crate::capability::CapHookExt).
//!
//! Use `cap_attach` once (first plugin that owns the capability) to prepend the empty
//! builder to the stack. Later plugins — including addons in other crates — use
//! `cap_hook` to register their hook without re-attaching the capability:
//!
//! ```ignore
//! // Hub plugin (creates the capability + base hook)
//! define_plugin_install! {
//! plugin: AccountsTag;
//! steps: [
//! cap_attach(SidebarTag, SidebarCap, SidebarCap::<frunk::HNil>::new()),
//! cap_hook(SidebarTag, SidebarCap, sidebar::BaseHook),
//! apps(apps::Hook),
//! // ...
//! ]
//! }
//!
//! // Addon plugin (patches the shared capability)
//! define_plugin_install! {
//! plugin: CustomerTag;
//! steps: [
//! cap_hook(accounts::SidebarTag, accounts::SidebarCap, accounting_sidebar::Hook),
//! apps(apps::Hook),
//! // ...
//! ]
//! }
//! ```
//!
//! At [`App::mount`](crate::app::App::mount), hooks fold over the capability's items in
//! reverse install order (tail first). `$Cap` is the type constructor (e.g. `SidebarCap`
//! for `SidebarCap<Hooks>`); `$expr` is typically `SidebarCap::<frunk::HNil>::new()`.
//!
//! See [`crate::plugin_install`] for the full step reference.
//!
//! Plugins that need shared runtime state (DB handle, config, caches) define a
//! [`StateHook`](crate::hooks::AttachState) and use `define_passthrough_cap!`:
//!
//! ```ignore
//! use lariv_rs::capability::{CapStore, define_passthrough_cap};
//!
//! pub struct MyState { /* fields */ }
//!
//! define_passthrough_cap!(MyStateCap, MyPluginTag, MyState);
//!
//! pub struct StateHook;
//!
//! impl AttachState<L, Proof> for StateHook {
//! type Output = HCons<MyStateCap, L>;
//! fn attach_state(app: App<L>) -> App<Self::Output> {
//! // read DbTag + ConfigTag, construct MyState, add_capability(...)
//! }
//! }
//! ```
//!
//! Simpler plugins (like dashboard) can attach state eagerly in the install macro:
//!
//! ```ignore
//! define_plugin_install! {
//! plugin: DashboardTag;
//! steps: [templates(templates::Hook), http(routes::Hook)];
//! finish: add_capability(DashboardStateCap, CapStore::with_items(DashboardState));
//! }
//! ```
//!
//! # Dashboard app tile
//!
//! Visible plugins register a tile so operators can launch them from `/dashboard`:
//!
//! ```ignore
//! // apps.rs
//! define_register_apps! {
//! plugin: MyPluginTag;
//! key: "my_plugin";
//! name: "My Plugin";
//! href: "/my-plugin";
//! icon: "sparkles";
//! roles: ["admin"];
//! }
//! ```
//!
//! Add `apps(apps::Hook)` to the install steps and include `apps.rs` in the plugin module.