{"owner":"eythaann","repo":"Seelen-UI","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# AGENTS.md\n\nGuidance for AI agents working in this repository.\n\nSeelen UI is a customizable Windows desktop environment built with:\n\n- Rust + Tauri (backend)\n- TypeScript + React/Preact (frontend)\n- A monorepo layout with shared libs under `libs/`\n\n## Read First (Non-Negotiable)\n\nBuild speed / safety:\n\n- DO NOT use `cargo build --release` for testing, type-checking, or local iteration.\n- Prefer `cargo check` for fast Rust validation.\n- Use `cargo build` (debug) only when you need a binary.\n\nTranslations:\n\n- DO NOT run `npm run translate` during active development.\n- Add translations manually while iterating; run the translate command only right before a final commit.\n\nRust locking order (avoid deadlocks):\n\n1. CLI locks\n2. DATA locks\n3. EVENT locks\n\nBackend architecture rules:\n\n- System modules in `src/background/modules/` MUST follow the modern pattern (lazy init + lazy tauri registration).\n- Business logic must NOT call `emit_to_webviews` directly.\n\nWinRT / COM safety:\n\n- For WinRT objects with event subscriptions, use wrapper structs with `Drop` for automatic unregistration.\n- Windows-rs clones `TypedEventHandler` internally: store tokens, not handlers.\n\n## Common Commands\n\nInitial setup:\n\n```bash\nnpm install && npm run dev\n```\n\nDev / build:\n\n- `npm run dev` - Frontend dev workflow\n- `npm run build:ui` - Build UI bundles\n- `npm run tauri dev` - Run Tauri in dev mode\n- `cargo check` - Fast Rust type check\n- `cargo build` - Debug build\n\nQuality (Deno-based):\n\n- `deno lint`\n- `deno fmt`\n- `npm run type-check`\n- `npm test`\n\nCore library (`libs/core`):\n\n- `deno task build`\n- `deno task build:rs` - Regenerate Rust -> TypeScript bindings\n- `deno task build:npm`\n\n## Repo Map (Where Things Live)\n\nShared libraries:\n\n- `libs/core/` - Core library + Rust-generated TypeScript bindings\n- `libs/widgets-shared/` - Cross-widget state utilities (includes LazySignal)\n- `libs/slu-ipc/`, `libs/positioning/`, `libs/widgets-integrity/`\n\nMain app:\n\n- `src/background/` - Rust backend (modules, native integrations)\n- `src/service/` - System service components\n- `src/ui/` - Frontend apps (each subdirectory is an independent app)\n  - examples: `src/ui/settings/`, `src/ui/toolbar/`, `src/ui/launcher/`, `src/ui/window_manager/`\n\n## Frontend Conventions\n\nApp architecture:\n\n- UI apps use a hexagonal-ish layering: `infra/`, `app/`, `domain/`, `shared/`.\n- Keep boundaries clean: `domain/` is pure logic; `infra/` is UI + integration.\n\nStyling:\n\n- CSS Modules are the default.\n- Naming: kebab-case for CSS, camelCase for TS.\n\nInternationalization:\n\n- All user-visible strings must be i18n.\n- Translation files live under `i18n/translations/` (YAML).\n\n## Backend: System Modules (Modern Pattern)\n\nAll modules in `src/background/modules/` follow this pattern:\n\n- `application.rs` owns the singleton manager and emits internal events.\n- `infrastructure.rs` (or `handlers.rs`) owns Tauri commands and bridges internal events -> webviews.\n- Tauri event registration happens lazily on first command access (via `Once`).\n\nSuggested layout:\n\n```\nsrc/background/modules/<module>/\n  mod.rs\n  application.rs\n  infrastructure.rs  # or handlers.rs\n  domain.rs          # optional\n```\n\nMinimal pattern (infrastructure side):\n\n```rust\nuse std::sync::Once;\nuse seelen_core::handlers::SeelenEvent;\nuse crate::{app::emit_to_webviews, error::Result};\nuse super::{YourEvent, YourManager};\n\nfn get_manager() -> &'static YourManager {\n    static REGISTER: Once = Once::new();\n    REGISTER.call_once(|| {\n        YourManager::subscribe(|_event: YourEvent| {\n            // Keep this small and side-effect focused.\n            if let Ok(data) = get_your_data() {\n                emit_to_webviews(SeelenEvent::YourDataChanged, data);\n            }\n        });\n    });\n    YourManager::instance()\n}\n\n#[tauri::command(async)]\npub fn get_your_data() -> Result<Vec<YourType>> {\n    let manager = get_manager();\n    Ok(manager.get_data())\n}\n```\n\nMinimal pattern (application side):\n\n```rust\nuse std::sync::LazyLock;\n\npub struct YourManager {\n    // fields\n}\n\n#[derive(Debug, Clone)]\npub enum YourEvent {\n    DataChanged,\n}\n\nevent_manager!(YourManager, YourEvent);\n\nimpl YourManager {\n    fn new() -> Self {\n        Self { /* init */ }\n    }\n\n    pub fn instance() -> &'static Self {\n        static MANAGER: LazyLock<YourManager> = LazyLock::new(|| {\n            let mut m = YourManager::new();\n            m.init().log_error();\n            m\n        });\n        &MANAGER\n    }\n\n    fn init(&mut self) -> Result<()> {\n        self.setup_listeners()?;\n        Ok(())\n    }\n\n    fn setup_listeners(&mut self) -> Result<()> {\n        // Listen to OS signals; emit internal YourEvent::* (not webview events)\n        Ok(())\n    }\n\n    pub fn get_data(&self) -> Vec<YourType> {\n        // return data\n        vec![]\n    }\n}\n```\n\nWhen adding a new backend feature exposed to the UI, update `libs/core`:\n\n1. `libs/core/src/handlers/commands.rs`\n\n```rust\nslu_commands_declaration! {\n    GetYourData = get_your_data() -> Vec<YourType>,\n}\n```\n\n2. `libs/core/src/handlers/events.rs`\n\n```rust\nslu_events_declaration! {\n    YourDataChanged(Vec<YourType>) as \"your-module::data-changed\",\n}\n```\n\n3. Regenerate bindings: `cd libs/core && deno task build:rs`\n\n## WinRT Wrapper Pattern (Automatic Cleanup)\n\nUse wrappers for WinRT objects that register events.\n\nRules:\n\n- Store event tokens (WinRT tokens are often `i64`).\n- Do NOT store `TypedEventHandler` values in struct fields.\n- Implement `Drop` to unregister events.\n\nExample:\n\n```rust\npub struct WinRtWrapper {\n    pub object: SomeWinRtObject,\n    token: i64,\n}\n\nimpl WinRtWrapper {\n    pub fn create(object: SomeWinRtObject) -> Result<Self> {\n        let token = object.SomeEvent(&TypedEventHandler::new(Self::on_event))?;\n        Ok(Self { object, token })\n    }\n\n    fn on_event(\n        _sender: &Option<SomeWinRtObject>,\n        _args: &Option<SomeArgs>,\n    ) -> windows_core::Result<()> {\n        Ok(())\n    }\n}\n\nimpl Drop for WinRtWrapper {\n    fn drop(&mut self) {\n        self.object.RemoveSomeEvent(self.token).log_error();\n    }\n}\n```\n\n## Shared State: LazySignal (Cross-Widget)\n\nUse `LazySignal` (in `libs/widgets-shared/`) when state is:\n\n- fetched asynchronously (invoke/system APIs)\n- updated by async events\n- shared across widgets/webviews\n\nCritical usage pattern:\n\n1. Create lazy signal with async initializer.\n2. Register event listeners first (they may fire immediately).\n3. Call `.init()` last; it must not overwrite a value set by an event.\n\nExample:\n\n```ts\nimport { lazySignal } from \"libs/widgets-shared/LazySignal\";\nimport { invoke, SeelenCommand, SeelenEvent, subscribe } from \"@seelen-ui/lib\";\n\nconst $data = lazySignal(async () => {\n  return await invoke(SeelenCommand.GetYourData);\n});\n\nsubscribe(SeelenEvent.YourDataChanged, (event) => {\n  $data.value = event.payload;\n});\n\nawait $data.init();\n```\n\n## Svelte: State Encapsulation (Non-Negotiable)\n\nNever export `$state` variables directly from a module. Always wrap them in a class with getters and export a single\ninstance of that class. This applies to both raw `$state` and reactive wrappers like `lazyRune`.\n\n**Bad:**\n\n```ts\nexport let count = $state(0);\nexport const items = lazyRune(() => invoke(SomeCommand));\n```\n\n**Good:**\n\n```ts\nconst items = lazyRune<Item[]>(() => invoke(SomeCommand));\nlet count = $state(0);\n\nclass MyState {\n  get items() {\n    return items;\n  }\n  get count() {\n    return count;\n  }\n}\nexport const myState = new MyState();\n```\n\nState and reactive values live at module scope; the class only exposes getters (and setters when mutation is needed).\n\n## Svelte: Only Svelte 5+ APIs (Non-Negotiable)\n\nAll Svelte code in this repo targets **Svelte 5**. Never write Svelte 4 / legacy patterns:\n\n| Forbidden (legacy)              | Required (Svelte 5+)                  |\n| ------------------------------- | ------------------------------------- |\n| `use:action` for DOM attachment | `{@attach fn}`                        |\n| `export let prop`               | `let { prop } = $props()`             |\n| `$:` reactive statements        | `$derived` / `$effect`                |\n| `createEventDispatcher`         | callback props                        |\n| `<slot>`                        | `{@render children()}` with `Snippet` |\n\nWhen attaching imperative DOM integrations (e.g. `@dnd-kit/svelte` sortable), always use `{@attach sortable.attach}`\ndirectly — never wrap it in a `use:action` helper.\n\n## Creating Svelte Widgets (High-Level)\n\nSeelen UI supports standalone Svelte widgets. Prefer following existing widget patterns; do not invent new build\nplumbing.\n\nTypical pieces:\n\n1. Static widget definition: `src/static/widgets/<widget-name>/`\n2. Svelte app: `src/ui/svelte/<widget-name>/`\n3. Theme styles: `src/static/themes/default/styles/<widget-name>.scss`\n4. i18n: translations (and keep the translation workflow rule)\n5. Optional Rust backend integration (use the modern module pattern)\n\nWidget checklist:\n\n- Static metadata and HTML exist under `src/static/widgets/<widget-name>/`\n- Svelte entry mounts into `#root` and calls `Widget.getCurrent().init(...)`\n- Shared, event-driven state uses LazySignal\n- Styling uses existing CSS variables; avoid global class conflicts\n\nShared styling for widgets:\n\n- Use `data-skin` attributes for common control styling (buttons, inputs) to avoid class collisions.\n\n## Rust Types: Tagged Enums (Serde)\n\nAvoid tuple variants for internally tagged enums.\n\nBad:\n\n```rust\n#[serde(tag = \"type\")]\npub enum Action {\n    WithData(String),\n}\n```\n\nGood:\n\n```rust\n#[serde(tag = \"type\")]\npub enum Action {\n    WithData { data: String },\n}\n```\n\n## Testing Expectations\n\n- Prefer quick feedback loops (`cargo check`, `npm run type-check`, `deno lint`).\n- Keep changes scoped; add tests when behavior changes.\n\n## Documentation & Custom Resources\n\nUser/developer-facing docs live under `documentation/`. Always check there before asking how something works:\n\n- `FEATURES.md` — Full feature reference: every widget, setting, shortcut, and system capability\n- `resource-guidelines.md` — Shared concepts: resource IDs, YAML `!include`/`!extend`, i18n, `slu` CLI\n- `widget-guidelines.md` / `widget-js-api.md` — How to build a custom widget: the resource guideline plus the runtime JS\n  API (`init`/`ready`, `invoke`/`subscribe`)\n- `theme-guidelines.md` — How to create a theme\n- `plugin-guidelines.md`, `toolbar-plugins.md`, `dock-plugins.md`, `wm-layouts.md` — Plugins: flat per-widget extension\n  files, with the shared guideline plus per-target-widget schemas (toolbar, dock, window manager layouts)\n\nThe three external resource types and their required files:\n\n| Type   | Minimum files                                           |\n| ------ | ------------------------------------------------------- |\n| Widget | `metadata.yml`, `i18n/`                                 |\n| Theme  | `metadata.yml`, `styles/<widget-id>.scss`               |\n| Plugin | `metadata.yml`, payload files referenced via `!include` |\n\nAll resource IDs follow `@username/resource-name`.\n\nDev workflow with the `slu` CLI (Seelen UI must be running):\n\n```bash\nslu resource load theme ./my-theme     # live-load without bundling\nslu resource load widget ./my-widget\nslu resource unload theme ./my-theme\nslu resource bundle theme ./my-theme   # produce .yaml for marketplace\n```\n\nBuilt-in resources (used as reference implementations):\n\n- `src/static/widgets/` — built-in widget definitions\n- `src/static/themes/` — built-in themes\n- `src/static/plugins/` — built-in toolbar plugins\n"},"files":{"AGENTS.md":"# AGENTS.md\n\nGuidance for AI agents working in this repository.\n\nSeelen UI is a customizable Windows desktop environment built with:\n\n- Rust + Tauri (backend)\n- TypeScript + React/Preact (frontend)\n- A monorepo layout with shared libs under `libs/`\n\n## Read First (Non-Negotiable)\n\nBuild speed / safety:\n\n- DO NOT use `cargo build --release` for testing, type-checking, or local iteration.\n- Prefer `cargo check` for fast Rust validation.\n- Use `cargo build` (debug) only when you need a binary.\n\nTranslations:\n\n- DO NOT run `npm run translate` during active development.\n- Add translations manually while iterating; run the translate command only right before a final commit.\n\nRust locking order (avoid deadlocks):\n\n1. CLI locks\n2. DATA locks\n3. EVENT locks\n\nBackend architecture rules:\n\n- System modules in `src/background/modules/` MUST follow the modern pattern (lazy init + lazy tauri registration).\n- Business logic must NOT call `emit_to_webviews` directly.\n\nWinRT / COM safety:\n\n- For WinRT objects with event subscriptions, use wrapper structs with `Drop` for automatic unregistration.\n- Windows-rs clones `TypedEventHandler` internally: store tokens, not handlers.\n\n## Common Commands\n\nInitial setup:\n\n```bash\nnpm install && npm run dev\n```\n\nDev / build:\n\n- `npm run dev` - Frontend dev workflow\n- `npm run build:ui` - Build UI bundles\n- `npm run tauri dev` - Run Tauri in dev mode\n- `cargo check` - Fast Rust type check\n- `cargo build` - Debug build\n\nQuality (Deno-based):\n\n- `deno lint`\n- `deno fmt`\n- `npm run type-check`\n- `npm test`\n\nCore library (`libs/core`):\n\n- `deno task build`\n- `deno task build:rs` - Regenerate Rust -> TypeScript bindings\n- `deno task build:npm`\n\n## Repo Map (Where Things Live)\n\nShared libraries:\n\n- `libs/core/` - Core library + Rust-generated TypeScript bindings\n- `libs/widgets-shared/` - Cross-widget state utilities (includes LazySignal)\n- `libs/slu-ipc/`, `libs/positioning/`, `libs/widgets-integrity/`\n\nMain app:\n\n- `src/background/` - Rust backend (modules, native integrations)\n- `src/service/` - System service components\n- `src/ui/` - Frontend apps (each subdirectory is an independent app)\n  - examples: `src/ui/settings/`, `src/ui/toolbar/`, `src/ui/launcher/`, `src/ui/window_manager/`\n\n## Frontend Conventions\n\nApp architecture:\n\n- UI apps use a hexagonal-ish layering: `infra/`, `app/`, `domain/`, `shared/`.\n- Keep boundaries clean: `domain/` is pure logic; `infra/` is UI + integration.\n\nStyling:\n\n- CSS Modules are the default.\n- Naming: kebab-case for CSS, camelCase for TS.\n\nInternationalization:\n\n- All user-visible strings must be i18n.\n- Translation files live under `i18n/translations/` (YAML).\n\n## Backend: System Modules (Modern Pattern)\n\nAll modules in `src/background/modules/` follow this pattern:\n\n- `application.rs` owns the singleton manager and emits internal events.\n- `infrastructure.rs` (or `handlers.rs`) owns Tauri commands and bridges internal events -> webviews.\n- Tauri event registration happens lazily on first command access (via `Once`).\n\nSuggested layout:\n\n```\nsrc/background/modules/<module>/\n  mod.rs\n  application.rs\n  infrastructure.rs  # or handlers.rs\n  domain.rs          # optional\n```\n\nMinimal pattern (infrastructure side):\n\n```rust\nuse std::sync::Once;\nuse seelen_core::handlers::SeelenEvent;\nuse crate::{app::emit_to_webviews, error::Result};\nuse super::{YourEvent, YourManager};\n\nfn get_manager() -> &'static YourManager {\n    static REGISTER: Once = Once::new();\n    REGISTER.call_once(|| {\n        YourManager::subscribe(|_event: YourEvent| {\n            // Keep this small and side-effect focused.\n            if let Ok(data) = get_your_data() {\n                emit_to_webviews(SeelenEvent::YourDataChanged, data);\n            }\n        });\n    });\n    YourManager::instance()\n}\n\n#[tauri::command(async)]\npub fn get_your_data() -> Result<Vec<YourType>> {\n    let manager = get_manager();\n    Ok(manager.get_data())\n}\n```\n\nMinimal pattern (application side):\n\n```rust\nuse std::sync::LazyLock;\n\npub struct YourManager {\n    // fields\n}\n\n#[derive(Debug, Clone)]\npub enum YourEvent {\n    DataChanged,\n}\n\nevent_manager!(YourManager, YourEvent);\n\nimpl YourManager {\n    fn new() -> Self {\n        Self { /* init */ }\n    }\n\n    pub fn instance() -> &'static Self {\n        static MANAGER: LazyLock<YourManager> = LazyLock::new(|| {\n            let mut m = YourManager::new();\n            m.init().log_error();\n            m\n        });\n        &MANAGER\n    }\n\n    fn init(&mut self) -> Result<()> {\n        self.setup_listeners()?;\n        Ok(())\n    }\n\n    fn setup_listeners(&mut self) -> Result<()> {\n        // Listen to OS signals; emit internal YourEvent::* (not webview events)\n        Ok(())\n    }\n\n    pub fn get_data(&self) -> Vec<YourType> {\n        // return data\n        vec![]\n    }\n}\n```\n\nWhen adding a new backend feature exposed to the UI, update `libs/core`:\n\n1. `libs/core/src/handlers/commands.rs`\n\n```rust\nslu_commands_declaration! {\n    GetYourData = get_your_data() -> Vec<YourType>,\n}\n```\n\n2. `libs/core/src/handlers/events.rs`\n\n```rust\nslu_events_declaration! {\n    YourDataChanged(Vec<YourType>) as \"your-module::data-changed\",\n}\n```\n\n3. Regenerate bindings: `cd libs/core && deno task build:rs`\n\n## WinRT Wrapper Pattern (Automatic Cleanup)\n\nUse wrappers for WinRT objects that register events.\n\nRules:\n\n- Store event tokens (WinRT tokens are often `i64`).\n- Do NOT store `TypedEventHandler` values in struct fields.\n- Implement `Drop` to unregister events.\n\nExample:\n\n```rust\npub struct WinRtWrapper {\n    pub object: SomeWinRtObject,\n    token: i64,\n}\n\nimpl WinRtWrapper {\n    pub fn create(object: SomeWinRtObject) -> Result<Self> {\n        let token = object.SomeEvent(&TypedEventHandler::new(Self::on_event))?;\n        Ok(Self { object, token })\n    }\n\n    fn on_event(\n        _sender: &Option<SomeWinRtObject>,\n        _args: &Option<SomeArgs>,\n    ) -> windows_core::Result<()> {\n        Ok(())\n    }\n}\n\nimpl Drop for WinRtWrapper {\n    fn drop(&mut self) {\n        self.object.RemoveSomeEvent(self.token).log_error();\n    }\n}\n```\n\n## Shared State: LazySignal (Cross-Widget)\n\nUse `LazySignal` (in `libs/widgets-shared/`) when state is:\n\n- fetched asynchronously (invoke/system APIs)\n- updated by async events\n- shared across widgets/webviews\n\nCritical usage pattern:\n\n1. Create lazy signal with async initializer.\n2. Register event listeners first (they may fire immediately).\n3. Call `.init()` last; it must not overwrite a value set by an event.\n\nExample:\n\n```ts\nimport { lazySignal } from \"libs/widgets-shared/LazySignal\";\nimport { invoke, SeelenCommand, SeelenEvent, subscribe } from \"@seelen-ui/lib\";\n\nconst $data = lazySignal(async () => {\n  return await invoke(SeelenCommand.GetYourData);\n});\n\nsubscribe(SeelenEvent.YourDataChanged, (event) => {\n  $data.value = event.payload;\n});\n\nawait $data.init();\n```\n\n## Svelte: State Encapsulation (Non-Negotiable)\n\nNever export `$state` variables directly from a module. Always wrap them in a class with getters and export a single\ninstance of that class. This applies to both raw `$state` and reactive wrappers like `lazyRune`.\n\n**Bad:**\n\n```ts\nexport let count = $state(0);\nexport const items = lazyRune(() => invoke(SomeCommand));\n```\n\n**Good:**\n\n```ts\nconst items = lazyRune<Item[]>(() => invoke(SomeCommand));\nlet count = $state(0);\n\nclass MyState {\n  get items() {\n    return items;\n  }\n  get count() {\n    return count;\n  }\n}\nexport const myState = new MyState();\n```\n\nState and reactive values live at module scope; the class only exposes getters (and setters when mutation is needed).\n\n## Svelte: Only Svelte 5+ APIs (Non-Negotiable)\n\nAll Svelte code in this repo targets **Svelte 5**. Never write Svelte 4 / legacy patterns:\n\n| Forbidden (legacy)              | Required (Svelte 5+)                  |\n| ------------------------------- | ------------------------------------- |\n| `use:action` for DOM attachment | `{@attach fn}`                        |\n| `export let prop`               | `let { prop } = $props()`             |\n| `$:` reactive statements        | `$derived` / `$effect`                |\n| `createEventDispatcher`         | callback props                        |\n| `<slot>`                        | `{@render children()}` with `Snippet` |\n\nWhen attaching imperative DOM integrations (e.g. `@dnd-kit/svelte` sortable), always use `{@attach sortable.attach}`\ndirectly — never wrap it in a `use:action` helper.\n\n## Creating Svelte Widgets (High-Level)\n\nSeelen UI supports standalone Svelte widgets. Prefer following existing widget patterns; do not invent new build\nplumbing.\n\nTypical pieces:\n\n1. Static widget definition: `src/static/widgets/<widget-name>/`\n2. Svelte app: `src/ui/svelte/<widget-name>/`\n3. Theme styles: `src/static/themes/default/styles/<widget-name>.scss`\n4. i18n: translations (and keep the translation workflow rule)\n5. Optional Rust backend integration (use the modern module pattern)\n\nWidget checklist:\n\n- Static metadata and HTML exist under `src/static/widgets/<widget-name>/`\n- Svelte entry mounts into `#root` and calls `Widget.getCurrent().init(...)`\n- Shared, event-driven state uses LazySignal\n- Styling uses existing CSS variables; avoid global class conflicts\n\nShared styling for widgets:\n\n- Use `data-skin` attributes for common control styling (buttons, inputs) to avoid class collisions.\n\n## Rust Types: Tagged Enums (Serde)\n\nAvoid tuple variants for internally tagged enums.\n\nBad:\n\n```rust\n#[serde(tag = \"type\")]\npub enum Action {\n    WithData(String),\n}\n```\n\nGood:\n\n```rust\n#[serde(tag = \"type\")]\npub enum Action {\n    WithData { data: String },\n}\n```\n\n## Testing Expectations\n\n- Prefer quick feedback loops (`cargo check`, `npm run type-check`, `deno lint`).\n- Keep changes scoped; add tests when behavior changes.\n\n## Documentation & Custom Resources\n\nUser/developer-facing docs live under `documentation/`. Always check there before asking how something works:\n\n- `FEATURES.md` — Full feature reference: every widget, setting, shortcut, and system capability\n- `resource-guidelines.md` — Shared concepts: resource IDs, YAML `!include`/`!extend`, i18n, `slu` CLI\n- `widget-guidelines.md` / `widget-js-api.md` — How to build a custom widget: the resource guideline plus the runtime JS\n  API (`init`/`ready`, `invoke`/`subscribe`)\n- `theme-guidelines.md` — How to create a theme\n- `plugin-guidelines.md`, `toolbar-plugins.md`, `dock-plugins.md`, `wm-layouts.md` — Plugins: flat per-widget extension\n  files, with the shared guideline plus per-target-widget schemas (toolbar, dock, window manager layouts)\n\nThe three external resource types and their required files:\n\n| Type   | Minimum files                                           |\n| ------ | ------------------------------------------------------- |\n| Widget | `metadata.yml`, `i18n/`                                 |\n| Theme  | `metadata.yml`, `styles/<widget-id>.scss`               |\n| Plugin | `metadata.yml`, payload files referenced via `!include` |\n\nAll resource IDs follow `@username/resource-name`.\n\nDev workflow with the `slu` CLI (Seelen UI must be running):\n\n```bash\nslu resource load theme ./my-theme     # live-load without bundling\nslu resource load widget ./my-widget\nslu resource unload theme ./my-theme\nslu resource bundle theme ./my-theme   # produce .yaml for marketplace\n```\n\nBuilt-in resources (used as reference implementations):\n\n- `src/static/widgets/` — built-in widget definitions\n- `src/static/themes/` — built-in themes\n- `src/static/plugins/` — built-in toolbar plugins\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md\n\nGuidance for AI agents working in this repository.\n\nSeelen UI is a customizable Windows desktop environment built with:\n\n- Rust + Tauri (backend)\n- TypeScript + React/Preact (frontend)\n- A monorepo layout with shared libs under `libs/`\n\n## Read First (Non-Negotiable)\n\nBuild speed / safety:\n\n- DO NOT use `cargo build --release` for testing, type-checking, or local iteration.\n- Prefer `cargo check` for fast Rust validation.\n- Use `cargo build` (debug) only when you need a binary.\n\nTranslations:\n\n- DO NOT run `npm run translate` during active development.\n- Add translations manually while iterating; run the translate command only right before a final commit.\n\nRust locking order (avoid deadlocks):\n\n1. CLI locks\n2. DATA locks\n3. EVENT locks\n\nBackend architecture rules:\n\n- System modules in `src/background/modules/` MUST follow the modern pattern (lazy init + lazy tauri registration).\n- Business logic must NOT call `emit_to_webviews` directly.\n\nWinRT / COM safety:\n\n- For WinRT objects with event subscriptions, use wrapper structs with `Drop` for automatic unregistration.\n- Windows-rs clones `TypedEventHandler` internally: store tokens, not handlers.\n\n## Common Commands\n\nInitial setup:\n\n```bash\nnpm install && npm run dev\n```\n\nDev / build:\n\n- `npm run dev` - Frontend dev workflow\n- `npm run build:ui` - Build UI bundles\n- `npm run tauri dev` - Run Tauri in dev mode\n- `cargo check` - Fast Rust type check\n- `cargo build` - Debug build\n\nQuality (Deno-based):\n\n- `deno lint`\n- `deno fmt`\n- `npm run type-check`\n- `npm test`\n\nCore library (`libs/core`):\n\n- `deno task build`\n- `deno task build:rs` - Regenerate Rust -> TypeScript bindings\n- `deno task build:npm`\n\n## Repo Map (Where Things Live)\n\nShared libraries:\n\n- `libs/core/` - Core library + Rust-generated TypeScript bindings\n- `libs/widgets-shared/` - Cross-widget state utilities (includes LazySignal)\n- `libs/slu-ipc/`, `libs/positioning/`, `libs/widgets-integrity/`\n\nMain app:\n\n- `src/background/` - Rust backend (modules, native integrations)\n- `src/service/` - System service components\n- `src/ui/` - Frontend apps (each subdirectory is an independent app)\n  - examples: `src/ui/settings/`, `src/ui/toolbar/`, `src/ui/launcher/`, `src/ui/window_manager/`\n\n## Frontend Conventions\n\nApp architecture:\n\n- UI apps use a hexagonal-ish layering: `infra/`, `app/`, `domain/`, `shared/`.\n- Keep boundaries clean: `domain/` is pure logic; `infra/` is UI + integration.\n\nStyling:\n\n- CSS Modules are the default.\n- Naming: kebab-case for CSS, camelCase for TS.\n\nInternationalization:\n\n- All user-visible strings must be i18n.\n- Translation files live under `i18n/translations/` (YAML).\n\n## Backend: System Modules (Modern Pattern)\n\nAll modules in `src/background/modules/` follow this pattern:\n\n- `application.rs` owns the singleton manager and emits internal events.\n- `infrastructure.rs` (or `handlers.rs`) owns Tauri commands and bridges internal events -> webviews.\n- Tauri event registration happens lazily on first command access (via `Once`).\n\nSuggested layout:\n\n```\nsrc/background/modules/<module>/\n  mod.rs\n  application.rs\n  infrastructure.rs  # or handlers.rs\n  domain.rs          # optional\n```\n\nMinimal pattern (infrastructure side):\n\n```rust\nuse std::sync::Once;\nuse seelen_core::handlers::SeelenEvent;\nuse crate::{app::emit_to_webviews, error::Result};\nuse super::{YourEvent, YourManager};\n\nfn get_manager() -> &'static YourManager {\n    static REGISTER: Once = Once::new();\n    REGISTER.call_once(|| {\n        YourManager::subscribe(|_event: YourEvent| {\n            // Keep this small and side-effect focused.\n            if let Ok(data) = get_your_data() {\n                emit_to_webviews(SeelenEvent::YourDataChanged, data);\n            }\n        });\n    });\n    YourManager::instance()\n}\n\n#[tauri::command(async)]\npub fn get_your_data() -> Result<Vec<YourType>> {\n    let manager = get_manager();\n    Ok(manager.get_data())\n}\n```\n\nMinimal pattern (application side):\n\n```rust\nuse std::sync::LazyLock;\n\npub struct YourManager {\n    // fields\n}\n\n#[derive(Debug, Clone)]\npub enum YourEvent {\n    DataChanged,\n}\n\nevent_manager!(YourManager, YourEvent);\n\nimpl YourManager {\n    fn new() -> Self {\n        Self { /* init */ }\n    }\n\n    pub fn instance() -> &'static Self {\n        static MANAGER: LazyLock<YourManager> = LazyLock::new(|| {\n            let mut m = YourManager::new();\n            m.init().log_error();\n            m\n        });\n        &MANAGER\n    }\n\n    fn init(&mut self) -> Result<()> {\n        self.setup_listeners()?;\n        Ok(())\n    }\n\n    fn setup_listeners(&mut self) -> Result<()> {\n        // Listen to OS signals; emit internal YourEvent::* (not webview events)\n        Ok(())\n    }\n\n    pub fn get_data(&self) -> Vec<YourType> {\n        // return data\n        vec![]\n    }\n}\n```\n\nWhen adding a new backend feature exposed to the UI, update `libs/core`:\n\n1. `libs/core/src/handlers/commands.rs`\n\n```rust\nslu_commands_declaration! {\n    GetYourData = get_your_data() -> Vec<YourType>,\n}\n```\n\n2. `libs/core/src/handlers/events.rs`\n\n```rust\nslu_events_declaration! {\n    YourDataChanged(Vec<YourType>) as \"your-module::data-changed\",\n}\n```\n\n3. Regenerate bindings: `cd libs/core && deno task build:rs`\n\n## WinRT Wrapper Pattern (Automatic Cleanup)\n\nUse wrappers for WinRT objects that register events.\n\nRules:\n\n- Store event tokens (WinRT tokens are often `i64`).\n- Do NOT store `TypedEventHandler` values in struct fields.\n- Implement `Drop` to unregister events.\n\nExample:\n\n```rust\npub struct WinRtWrapper {\n    pub object: SomeWinRtObject,\n    token: i64,\n}\n\nimpl WinRtWrapper {\n    pub fn create(object: SomeWinRtObject) -> Result<Self> {\n        let token = object.SomeEvent(&TypedEventHandler::new(Self::on_event))?;\n        Ok(Self { object, token })\n    }\n\n    fn on_event(\n        _sender: &Option<SomeWinRtObject>,\n        _args: &Option<SomeArgs>,\n    ) -> windows_core::Result<()> {\n        Ok(())\n    }\n}\n\nimpl Drop for WinRtWrapper {\n    fn drop(&mut self) {\n        self.object.RemoveSomeEvent(self.token).log_error();\n    }\n}\n```\n\n## Shared State: LazySignal (Cross-Widget)\n\nUse `LazySignal` (in `libs/widgets-shared/`) when state is:\n\n- fetched asynchronously (invoke/system APIs)\n- updated by async events\n- shared across widgets/webviews\n\nCritical usage pattern:\n\n1. Create lazy signal with async initializer.\n2. Register event listeners first (they may fire immediately).\n3. Call `.init()` last; it must not overwrite a value set by an event.\n\nExample:\n\n```ts\nimport { lazySignal } from \"libs/widgets-shared/LazySignal\";\nimport { invoke, SeelenCommand, SeelenEvent, subscribe } from \"@seelen-ui/lib\";\n\nconst $data = lazySignal(async () => {\n  return await invoke(SeelenCommand.GetYourData);\n});\n\nsubscribe(SeelenEvent.YourDataChanged, (event) => {\n  $data.value = event.payload;\n});\n\nawait $data.init();\n```\n\n## Svelte: State Encapsulation (Non-Negotiable)\n\nNever export `$state` variables directly from a module. Always wrap them in a class with getters and export a single\ninstance of that class. This applies to both raw `$state` and reactive wrappers like `lazyRune`.\n\n**Bad:**\n\n```ts\nexport let count = $state(0);\nexport const items = lazyRune(() => invoke(SomeCommand));\n```\n\n**Good:**\n\n```ts\nconst items = lazyRune<Item[]>(() => invoke(SomeCommand));\nlet count = $state(0);\n\nclass MyState {\n  get items() {\n    return items;\n  }\n  get count() {\n    return count;\n  }\n}\nexport const myState = new MyState();\n```\n\nState and reactive values live at module scope; the class only exposes getters (and setters when mutation is needed).\n\n## Svelte: Only Svelte 5+ APIs (Non-Negotiable)\n\nAll Svelte code in this repo targets **Svelte 5**. Never write Svelte 4 / legacy patterns:\n\n| Forbidden (legacy)              | Required (Svelte 5+)                  |\n| ------------------------------- | ------------------------------------- |\n| `use:action` for DOM attachment | `{@attach fn}`                        |\n| `export let prop`               | `let { prop } = $props()`             |\n| `$:` reactive statements        | `$derived` / `$effect`                |\n| `createEventDispatcher`         | callback props                        |\n| `<slot>`                        | `{@render children()}` with `Snippet` |\n\nWhen attaching imperative DOM integrations (e.g. `@dnd-kit/svelte` sortable), always use `{@attach sortable.attach}`\ndirectly — never wrap it in a `use:action` helper.\n\n## Creating Svelte Widgets (High-Level)\n\nSeelen UI supports standalone Svelte widgets. Prefer following existing widget patterns; do not invent new build\nplumbing.\n\nTypical pieces:\n\n1. Static widget definition: `src/static/widgets/<widget-name>/`\n2. Svelte app: `src/ui/svelte/<widget-name>/`\n3. Theme styles: `src/static/themes/default/styles/<widget-name>.scss`\n4. i18n: translations (and keep the translation workflow rule)\n5. Optional Rust backend integration (use the modern module pattern)\n\nWidget checklist:\n\n- Static metadata and HTML exist under `src/static/widgets/<widget-name>/`\n- Svelte entry mounts into `#root` and calls `Widget.getCurrent().init(...)`\n- Shared, event-driven state uses LazySignal\n- Styling uses existing CSS variables; avoid global class conflicts\n\nShared styling for widgets:\n\n- Use `data-skin` attributes for common control styling (buttons, inputs) to avoid class collisions.\n\n## Rust Types: Tagged Enums (Serde)\n\nAvoid tuple variants for internally tagged enums.\n\nBad:\n\n```rust\n#[serde(tag = \"type\")]\npub enum Action {\n    WithData(String),\n}\n```\n\nGood:\n\n```rust\n#[serde(tag = \"type\")]\npub enum Action {\n    WithData { data: String },\n}\n```\n\n## Testing Expectations\n\n- Prefer quick feedback loops (`cargo check`, `npm run type-check`, `deno lint`).\n- Keep changes scoped; add tests when behavior changes.\n\n## Documentation & Custom Resources\n\nUser/developer-facing docs live under `documentation/`. Always check there before asking how something works:\n\n- `FEATURES.md` — Full feature reference: every widget, setting, shortcut, and system capability\n- `resource-guidelines.md` — Shared concepts: resource IDs, YAML `!include`/`!extend`, i18n, `slu` CLI\n- `widget-guidelines.md` / `widget-js-api.md` — How to build a custom widget: the resource guideline plus the runtime JS\n  API (`init`/`ready`, `invoke`/`subscribe`)\n- `theme-guidelines.md` — How to create a theme\n- `plugin-guidelines.md`, `toolbar-plugins.md`, `dock-plugins.md`, `wm-layouts.md` — Plugins: flat per-widget extension\n  files, with the shared guideline plus per-target-widget schemas (toolbar, dock, window manager layouts)\n\nThe three external resource types and their required files:\n\n| Type   | Minimum files                                           |\n| ------ | ------------------------------------------------------- |\n| Widget | `metadata.yml`, `i18n/`                                 |\n| Theme  | `metadata.yml`, `styles/<widget-id>.scss`               |\n| Plugin | `metadata.yml`, payload files referenced via `!include` |\n\nAll resource IDs follow `@username/resource-name`.\n\nDev workflow with the `slu` CLI (Seelen UI must be running):\n\n```bash\nslu resource load theme ./my-theme     # live-load without bundling\nslu resource load widget ./my-widget\nslu resource unload theme ./my-theme\nslu resource bundle theme ./my-theme   # produce .yaml for marketplace\n```\n\nBuilt-in resources (used as reference implementations):\n\n- `src/static/widgets/` — built-in widget definitions\n- `src/static/themes/` — built-in themes\n- `src/static/plugins/` — built-in toolbar plugins\n","category":"root","tokens":2876}]}