diff --git a/.gitignore b/.gitignore
index 6f3f46fe4..d4e1b54bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,8 @@ build
out
bin
.DS_Store
-.codegpt
\ No newline at end of file
+.codegpt
+
+# Ignore declaration files accidentally emitted into source when aliasing from kando-svelte
+/src/common/**/*.d.ts
+/src/menu-renderer/**/*.d.ts
\ No newline at end of file
diff --git a/kando-svelte/.gitignore b/kando-svelte/.gitignore
new file mode 100644
index 000000000..47c6be13e
--- /dev/null
+++ b/kando-svelte/.gitignore
@@ -0,0 +1,25 @@
+node_modules
+
+# Output
+.output
+.vercel
+.netlify
+.wrangler
+/.svelte-kit
+/build
+/dist
+temp
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Env
+.env
+.env.*
+!.env.example
+!.env.test
+
+# Vite
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
diff --git a/kando-svelte/.npmrc b/kando-svelte/.npmrc
new file mode 100644
index 000000000..b6f27f135
--- /dev/null
+++ b/kando-svelte/.npmrc
@@ -0,0 +1 @@
+engine-strict=true
diff --git a/kando-svelte/.prettierignore b/kando-svelte/.prettierignore
new file mode 100644
index 000000000..7d74fe246
--- /dev/null
+++ b/kando-svelte/.prettierignore
@@ -0,0 +1,9 @@
+# Package Managers
+package-lock.json
+pnpm-lock.yaml
+yarn.lock
+bun.lock
+bun.lockb
+
+# Miscellaneous
+/static/
diff --git a/kando-svelte/.prettierrc b/kando-svelte/.prettierrc
new file mode 100644
index 000000000..3f7802c37
--- /dev/null
+++ b/kando-svelte/.prettierrc
@@ -0,0 +1,15 @@
+{
+ "useTabs": true,
+ "singleQuote": true,
+ "trailingComma": "none",
+ "printWidth": 100,
+ "plugins": ["prettier-plugin-svelte"],
+ "overrides": [
+ {
+ "files": "*.svelte",
+ "options": {
+ "parser": "svelte"
+ }
+ }
+ ]
+}
diff --git a/kando-svelte/README.md b/kando-svelte/README.md
new file mode 100644
index 000000000..fcf7336f8
--- /dev/null
+++ b/kando-svelte/README.md
@@ -0,0 +1,78 @@
+# kando-svelte (library)
+
+Svelte 5 library that renders Kando pie menus in web/SvelteKit apps.
+
+Goals
+- Be compatible with Kando data, themes and algorithms.
+- Reuse Kando source directly (math, types, schemata) where practical.
+- Keep rendering and event wiring idiomatic Svelte 5.
+
+What it is
+- A Svelte 5 library exporting components and helpers:
+ - `PieMenu.svelte`: render a Kando menu tree; emits `select`, `cancel`, `hover`, `unhover` events.
+ - `PieItem.svelte`, `SelectionWedges.svelte`, `WedgeSeparators.svelte`: building blocks.
+ - `Vendor`: convenient URLs for a bundled default theme and a minimal "none" sound theme.
+ - `theme-loader`: utilities to load JSON5 theme metadata, inject theme.css, and apply color overrides.
+ - `validation`: re-exports Kando zod schemata and simple parse helpers (optional to use).
+
+What it is not
+- It does not perform file system discovery, snapshot management, or OS integrations.
+- It does not implement execution of platform-specific item actions (command/file/hotkey/macro/settings).
+- It does not bundle icon fonts/CSS; the host app should include Material Symbols / Simple Icons if used by the chosen theme.
+
+Compatibility
+- Types: re-exported from Kando (`@kando/common`, `@kando/schemata/*`).
+- Math: imported from Kando (`src/common/math`).
+- Themes: accepts a `MenuThemeDescription` object or can load from a theme directory via `themeDirUrl` + `themeId`.
+
+Install & build (library)
+```bash
+# from the library folder
+npm install
+npm run build # builds .svelte-kit and dist via svelte-package
+```
+
+Usage (consumer app)
+```svelte
+
+
+ console.log('select', e.detail)} />
+```
+
+Theme loading
+- `PieMenu` props:
+ - `theme` (object) OR `themeDirUrl` + `themeId` (string).
+ - When loading by URL, `theme-loader` fetches `theme.json5`, injects `theme.css`, and applies colors.
+- You can also use the exported `Vendor.defaultThemeCss` and `Vendor.defaultThemeJson` URLs for a built-in default theme.
+
+Sound themes
+- The library bundles a minimal "none" sound theme JSON (`Vendor.noneSoundThemeJson`).
+- For real sound themes, load them in your app and use Howler in client-only lifecycle hooks.
+
+Icons
+- Include the icon CSS your themes expect in the app (e.g. in `app.html`):
+ - Material Symbols Rounded CSS (Google Fonts) or the `material-symbols` npm package.
+ - `simple-icons-font` if you use Simple Icons.
+
+Svelte 5
+- Internals use Svelte 5 runes for state/derived/effect where appropriate.
+- Public API remains plain props/events for maximum compatibility.
+
+Notes on path aliases
+- This repo uses `kit.alias` in `svelte.config.js` to reference Kando sources during development.
+- If you publish this library to npm, either bundle those sources or depend on a separate `kando-core` package.
+
+License & attribution
+- Kando is MIT; themes and font assets have their own licenses (e.g., CC0-1.0 for the default theme).
+- Preserve SPDX headers and attributions when copying code.
diff --git a/kando-svelte/eslint.config.js b/kando-svelte/eslint.config.js
new file mode 100644
index 000000000..e02473646
--- /dev/null
+++ b/kando-svelte/eslint.config.js
@@ -0,0 +1,4 @@
+import prettier from 'eslint-config-prettier';
+import svelte from 'eslint-plugin-svelte';
+
+export default [prettier, ...svelte.configs.prettier];
diff --git a/kando-svelte/menus-orig.json b/kando-svelte/menus-orig.json
new file mode 100644
index 000000000..5551330fd
--- /dev/null
+++ b/kando-svelte/menus-orig.json
@@ -0,0 +1,350 @@
+{
+ "menus": [
+ {
+ "root": {
+ "type": "submenu",
+ "name": "Example Menu",
+ "icon": "award_star",
+ "iconTheme": "material-symbols-rounded",
+ "children": [
+ {
+ "type": "submenu",
+ "name": "Apps",
+ "icon": "apps",
+ "iconTheme": "material-symbols-rounded",
+ "children": [
+ {
+ "type": "command",
+ "data": {
+ "command": "open -a Safari"
+ },
+ "name": "Safari",
+ "icon": "safari",
+ "iconTheme": "simple-icons"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "open -a Mail"
+ },
+ "name": "E-Mail",
+ "icon": "mail",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "open -a Music"
+ },
+ "name": "Music",
+ "icon": "itunes",
+ "iconTheme": "simple-icons"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "open -a Finder"
+ },
+ "name": "Finder",
+ "icon": "folder_shared",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "open -a Terminal"
+ },
+ "name": "Terminal",
+ "icon": "terminal",
+ "iconTheme": "material-symbols-rounded"
+ }
+ ]
+ },
+ {
+ "type": "submenu",
+ "name": "Web Links",
+ "icon": "public",
+ "iconTheme": "material-symbols-rounded",
+ "children": [
+ {
+ "type": "uri",
+ "data": {
+ "uri": "https://www.google.com"
+ },
+ "name": "Google",
+ "icon": "google",
+ "iconTheme": "simple-icons"
+ },
+ {
+ "type": "uri",
+ "data": {
+ "uri": "https://github.com/kando-menu/kando"
+ },
+ "name": "Kando on GitHub",
+ "icon": "github",
+ "iconTheme": "simple-icons"
+ },
+ {
+ "type": "uri",
+ "data": {
+ "uri": "https://ko-fi.com/schneegans"
+ },
+ "name": "Kando on Ko-fi",
+ "icon": "kofi",
+ "iconTheme": "simple-icons"
+ },
+ {
+ "type": "uri",
+ "data": {
+ "uri": "https://www.youtube.com/@simonschneegans"
+ },
+ "name": "Kando on YouTube",
+ "icon": "youtube",
+ "iconTheme": "simple-icons"
+ },
+ {
+ "type": "uri",
+ "data": {
+ "uri": "https://discord.gg/hZwbVSDkhy"
+ },
+ "name": "Kando on Discord",
+ "icon": "discord",
+ "iconTheme": "simple-icons"
+ }
+ ]
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"System Events\" to key code 124 using control down'"
+ },
+ "name": "Next Workspace",
+ "icon": "arrow_forward",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "submenu",
+ "name": "Clipboard",
+ "icon": "assignment",
+ "iconTheme": "material-symbols-rounded",
+ "children": [
+ {
+ "type": "hotkey",
+ "data": {
+ "hotkey": "MetaLeft+KeyV",
+ "delayed": true
+ },
+ "name": "Paste",
+ "icon": "content_paste_go",
+ "iconTheme": "material-symbols-rounded",
+ "angle": 90
+ },
+ {
+ "type": "hotkey",
+ "data": {
+ "hotkey": "MetaLeft+KeyC",
+ "delayed": true
+ },
+ "name": "Copy",
+ "icon": "content_copy",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "hotkey",
+ "data": {
+ "hotkey": "MetaLeft+KeyX",
+ "delayed": true
+ },
+ "name": "Cut",
+ "icon": "cut",
+ "iconTheme": "material-symbols-rounded"
+ }
+ ]
+ },
+ {
+ "type": "submenu",
+ "name": "Audio",
+ "icon": "play_circle",
+ "iconTheme": "material-symbols-rounded",
+ "children": [
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"Music\" to next track'"
+ },
+ "name": "Next Track",
+ "icon": "skip_next",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"Music\" to playpause'"
+ },
+ "name": "Play / Pause",
+ "icon": "play_pause",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"Music\" to previous track'"
+ },
+ "name": "Previous Track",
+ "icon": "skip_previous",
+ "iconTheme": "material-symbols-rounded"
+ }
+ ]
+ },
+ {
+ "type": "submenu",
+ "name": "Windows",
+ "icon": "select_window",
+ "iconTheme": "material-symbols-rounded",
+ "children": [
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"System Events\" to key code 126 using control down'"
+ },
+ "name": "Mission Control",
+ "icon": "select_window",
+ "iconTheme": "material-symbols-rounded",
+ "angle": 0
+ },
+ {
+ "type": "hotkey",
+ "data": {
+ "hotkey": "ControlLeft+AltLeft+ArrowRight",
+ "delayed": true
+ },
+ "name": "Tile Right",
+ "icon": "text_select_jump_to_end",
+ "iconTheme": "material-symbols-rounded",
+ "angle": 90
+ },
+ {
+ "type": "hotkey",
+ "data": {
+ "hotkey": "MetaLeft+KeyW",
+ "delayed": true
+ },
+ "name": "Close Window",
+ "icon": "cancel_presentation",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "hotkey",
+ "data": {
+ "hotkey": "ControlLeft+AltLeft+ArrowLeft",
+ "delayed": true
+ },
+ "name": "Tile Left",
+ "icon": "text_select_jump_to_beginning",
+ "iconTheme": "material-symbols-rounded",
+ "angle": 270
+ }
+ ]
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"System Events\" to key code 123 using control down'"
+ },
+ "name": "Previous Workspace",
+ "icon": "arrow_back",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "submenu",
+ "name": "Bookmarks",
+ "icon": "folder_special",
+ "iconTheme": "material-symbols-rounded",
+ "children": [
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"Finder\" to open (path to downloads folder as text)'"
+ },
+ "name": "Downloads",
+ "icon": "download",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"Finder\" to open (path to movies folder as text)'"
+ },
+ "name": "Videos",
+ "icon": "video_camera_front",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"Finder\" to open (path to pictures folder as text)'"
+ },
+ "name": "Pictures",
+ "icon": "imagesmode",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"Finder\" to open (path to documents folder as text)'"
+ },
+ "name": "Docuexample-menu.bookmarks.documentsments",
+ "icon": "text_ad",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"Finder\" to open (path to desktop folder as text)'"
+ },
+ "name": "Desktop",
+ "icon": "desktop_windows",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "open $HOME"
+ },
+ "name": "Home",
+ "icon": "home",
+ "iconTheme": "material-symbols-rounded"
+ },
+ {
+ "type": "command",
+ "data": {
+ "command": "osascript -e 'tell application \"Finder\" to open (path to music folder as text)'"
+ },
+ "name": "Music",
+ "icon": "music_note",
+ "iconTheme": "material-symbols-rounded"
+ }
+ ]
+ }
+ ]
+ },
+ "shortcut": "Control+Space",
+ "shortcutID": "example-menu",
+ "centered": false,
+ "anchored": false,
+ "hoverMode": false,
+ "tags": []
+ }
+ ],
+ "collections": [
+ {
+ "name": "Favorites",
+ "icon": "favorite",
+ "iconTheme": "material-symbols-rounded",
+ "tags": [
+ "favs"
+ ]
+ }
+ ],
+ "version": "2.1.0-beta.1"
+}
diff --git a/kando-svelte/notes/aquery.md b/kando-svelte/notes/aquery.md
new file mode 100644
index 000000000..52f8b8fc5
--- /dev/null
+++ b/kando-svelte/notes/aquery.md
@@ -0,0 +1,386 @@
+## aQuery: A Cross‑Platform Accessibility and Window API atop Kando
+
+This proposal defines “aQuery” — a high‑level, cross‑platform TypeScript API (à la jQuery/Cordova) that abstracts platform accessibility (a11y), window management, and input simulation behind one contract. Kando provides the execution substrate (Electron main/renderer, native backends) and the permission UX; aQuery provides a stable JS interface and selector semantics.
+
+### Rationale
+- Fragmented OS capabilities (AX on macOS, UIA on Windows, AT‑SPI on Linux; Wayland vs X11) complicate app automations and accessibility tooling.
+- Kando already ships native backends and a robust input pipeline; aQuery extends this into a reusable, documented API for first‑party features (triggers, editor helpers) and third‑party integrations.
+
+### Design Principles
+- Cross‑platform contract first; graceful capability detection (`supports.*`) and fallbacks.
+- Explicit, per‑feature permission prompts (and persistent user consent) with clear failure modes.
+- Asynchronous, promise‑first API; event‑driven subscriptions.
+- Strong typing; no platform enums leaked through public surface.
+- No busy loops; everything cancellable; observable streams where appropriate.
+
+---
+
+## Top‑Level API Surface (TypeScript)
+
+```ts
+namespace aquery {
+ // Core & permissions
+ function supports(): Promise<{
+ a11y: boolean; window: boolean; input: boolean; screen: boolean;
+ triggers: boolean; clipboard: boolean; app: boolean; gamepad: boolean;
+ }>;
+ function requestPermissions(opts: {
+ a11y?: boolean; input?: boolean; screen?: boolean;
+ }): Promise<{ granted: string[]; denied: string[] }>;
+
+ // App / Window
+ namespace app {
+ function getForeground(): Promise<{ id: string; name: string }>;
+ function listInstalled(): Promise>;
+ }
+ namespace window {
+ type Win = { id: string; appId: string; title: string; bounds: { x:number; y:number; w:number; h:number } };
+ function getActive(): Promise;
+ function list(opts?: { appId?: string }): Promise;
+ function activate(id: string): Promise;
+ function moveResize(id: string, bounds: Partial): Promise;
+ }
+
+ // Input (synthetic)
+ namespace input {
+ type Button = 'left'|'middle'|'right'|'x1'|'x2';
+ function mouse(event: {
+ type: 'move'|'down'|'up'|'click'|'dblclick'|'scroll';
+ button?: Button; x?: number; y?: number; dx?: number; dy?: number;
+ scrollX?: number; scrollY?: number;
+ modifiers?: { ctrl?: boolean; alt?: boolean; shift?: boolean; meta?: boolean };
+ }): Promise;
+ function key(event: { code: string; down: boolean }): Promise;
+ }
+
+ // Accessibility queries
+ namespace a11y {
+ type Node = { role: string; name?: string; enabled?: boolean; focused?: boolean; rect?: { x:number;y:number;w:number;h:number } };
+ function query(selector: string, opts?: { root?: 'system'|string; timeoutMs?: number }): Promise;
+ function action(selector: string, act: 'press'|'expand'|'collapse'|'focus'|'setValue', value?: unknown): Promise;
+ }
+
+ // Screen / clipboard
+ namespace screen {
+ function displays(): Promise>;
+ // High‑performance capture primitives (prefer GPU/zero‑copy where available)
+ function captureRegion(rect: { x:number;y:number;w:number;h:number }, opts?: { format?: 'png'|'jpeg'|'raw'; displayId?: string }): Promise;
+ function captureWindow(id: string, opts?: { format?: 'png'|'jpeg'|'raw' }): Promise;
+ function captureDisplay(displayId: string, opts?: { format?: 'png'|'jpeg'|'raw' }): Promise;
+ // Tracking utilities (pixel‑level, optional ML backends)
+ function trackRegion(init: { rect: { x:number;y:number;w:number;h:number }, strategy?: 'template'|'feature'|'ocr' }): AsyncGenerator<{ rect: { x:number;y:number;w:number;h:number }, confidence: number }>;
+ }
+ namespace clipboard {
+ function read(): Promise;
+ function write(text: string): Promise;
+ }
+
+ // Vision/LLM (pluggable providers: local VLMs or cloud)
+ namespace llm {
+ type Provider = 'auto'|'openai'|'ollama'|'local';
+ function describe(image: ImageBitmap | ArrayBuffer, prompt?: string, opts?: { provider?: Provider }): Promise;
+ function locate(image: ImageBitmap | ArrayBuffer, query: string, opts?: { provider?: Provider }): Promise>;
+ function plan(image: ImageBitmap | ArrayBuffer, goal: string, opts?: { provider?: Provider }): Promise>;
+ }
+
+ // Triggers (ties into Kando’s unified triggers[] model)
+ namespace triggers {
+ type MouseTrigger = { button: 'left'|'middle'|'right'|'x1'|'x2', mods?: string[] };
+ type GamepadTrigger = { button: number; stick?: 'left'|'right'; tiltThreshold?: number };
+ function register(menuName: string, trigger: { kind: 'mouse'; value: MouseTrigger } | { kind: 'gamepad'; value: GamepadTrigger }): Promise;
+ function unregister(menuName: string): Promise;
+ }
+}
+```
+
+### Selector Grammar (a11y)
+- CSS‑inspired for accessibility trees across AX/UIA/AT‑SPI:
+ - Role: `button`, `menuitem`, `textbox`
+ - Name: `[name="OK"]`, regex: `[name~/^Save/]`
+ - State: `:enabled`, `:focused`, `:visible`
+ - Hierarchy: `app[name="Safari"] > window > button[name="OK"]`
+ - Short alias: `a$(selector)` returns first match; `a$All(selector)` returns all.
+
+---
+
+## Platform Adapters
+
+- macOS: AX API (AXUIElement), CGEventTap (global hooks), CGEvent post (synthetic), NSWorkspace notifications (active app), Quartz Display Services (screens). Existing Kando native code already provides: pointer warp, simulateKey, active window, app listing — extend with: simulateMouse, event tap, AX queries/actions.
+- Windows: UI Automation (COM), WH_MOUSE_LL / Raw Input, SendInput, GetForegroundWindow/EnumWindows, Graphics Capture API (DXGI Desktop Duplication / Windows.Graphics.Capture).
+- Linux X11: AT‑SPI2 (D‑Bus), XTest/XI2, EWMH for windows. Wayland: portals (GlobalShortcuts keyboard only, RemoteDesktop/VirtualPointer/VirtualKeyboard with permissions), AT‑SPI via accessibility stack; many features are compositor‑dependent — expose via `supports()`.
+
+IPC and isolation:
+- Electron main performs privileged calls; renderer uses `contextBridge` APIs.
+- All methods return Promises; cancellation via AbortSignal for long queries.
+
+Permissions UX:
+- `aquery.requestPermissions({ a11y: true, input: true })` triggers per‑OS guidance (e.g., macOS Accessibility & Input Monitoring; Screen Recording when capturing)
+- Store consent in settings; re‑prompt on denial; surface clear error codes.
+
+---
+
+## Integration with Kando
+
+- Triggers: the `triggers` registry is a thin facade over Kando’s unified `triggers[]` model (see button‑trigger‑support.md). Mouse/gamepad opener logic flows through Kando’s existing conditions matcher and `MenuWindow.showMenu`.
+- Gesture pipeline: after open, existing `PointerInput`/`GamepadInput` continue to produce `InputState` (angle/distance), so selection/marking/turbo remain unchanged.
+- Editor: add pickers for MouseTrigger and GamepadTrigger; reuse conditions UI.
+
+---
+
+## Svelte / Browser Variant
+
+In `kando-svelte`, implement the same TS surface using browser capabilities:
+- Mouse triggers: DOM pointer events and `contextmenu` suppression.
+- Gamepad triggers: Web Gamepad API.
+- a11y/window: limited; expose `supports().a11y = false` and provide no‑op or portal‑based fallbacks; the contract remains the same so apps can feature‑detect.
+- Screen capture: `html2canvas`/`OffscreenCanvas` for demo purposes only; do not rely on for privacy‑sensitive features. In Electron renderer, prefer native capture bridges.
+
+---
+
+## Roadmap
+1) Spec + types + `supports()`; Svelte demo polyfill for triggers
+2) macOS adapter: simulateMouse + event tap + basic AX queries (role/name) and window list/activate
+3) Windows adapter: hooks + SendInput + UIA minimal + Graphics Capture
+4) Linux X11 adapter; Wayland: document limitations and portals where feasible
+5) Editor pickers and unified `triggers[]` schema migration
+6) Expanded AX/UIA actions and robust query engine (performance + timeouts)
+
+Testing:
+- Contract tests per method with platform‑specific expected capability matrices
+- Golden tests for selector resolution on synthetic accessibility trees
+
+Security:
+- Only elevate when requested; never run hidden; log every privileged action origin (menu/editor) for audit when dev tools enabled.
+
+License & contribution:
+- MIT (inherit from Kando); adapters reside under platform folders; contributors can add new backends behind the same TS interface.
+
+---
+
+## Background, Prior Art, and References (aQuery vision)
+
+The aQuery idea ("like jQuery for accessibility") predates Kando and draws on decades of HCI and accessibility research. Key motivations and inspirations include:
+
+- Combine accessibility APIs with pixel‑based screen analysis to overcome each method’s limitations; use both to robustly select, recognize, and control UI elements.
+- Treat desktop UIs as augmentable spaces: overlay guidance, implement target‑aware pointing (e.g., Bubble Cursor), sideviews, previews, and task‑specific controllers without modifying apps.
+- Build a community library of high‑level, cross‑app widgets (e.g., a generic “video player” control) that adapt to VLC, QuickTime, browsers, etc., akin to jQuery UI widgets spanning browser differences.
+
+Selected sources and quotes (lightly edited for clarity):
+
+> "Screen scraping techniques are very powerful, but have limitations. Accessibility APIs are very powerful, but have different limitations. Using both approaches together, and tightly integrating with JavaScript, enables a much wider range of possibilities." — HN post by Don Hopkins (2016)
+
+> "Prefab realizes this vision using only the pixels of everyday interfaces… add functionality to applications like Photoshop, iTunes, and WMP… first step toward a future where anybody can modify any interface." — Morgan Dixon & James Fogarty, CHI 2010–2012
+
+Core references:
+- Morgan Dixon et al., Prefab and target‑aware pointing (CHI ’10–’12)
+ - Video: Prefab: What if We Could Modify Any Interface?
+ - Video: Content and Hierarchy in Prefab
+ - Paper: Prefab; Bubble Cursor; Target‑Aware Pointing
+- Potter, Shneiderman, Bederson: Pixel Data Access & Triggers (1999)
+- Speech control ecosystems (e.g., Dragonfly Python modules) for command repositories
+
+How it maps to aQuery:
+- a11y: selector engine over AX/UIA/AT‑SPI nodes (role/name/state), event binding, actions
+- screen: capture/track (template/feature/OCR), compositing overlays
+- input: synthetic mouse/keyboard; timing control for “press‑tilt‑release” and gesture playback
+- llm: describe/locate/plan actions from snapshots; drive `input.*` with verifiable, sandboxed plans
+
+---
+
+## Snapshotting & LLM Scenarios
+
+1) Visual targeting fallback: if `a11y.query('button[name="Play"]')` returns empty, use `screen.captureWindow()` + `llm.locate(…, 'play button')` to get a bounding box; click center via `input.mouse({ type:'click', button:'left', x, y })`.
+2) Robust selectors: combine `a11y` and `screen` features: match role/name, verify icon pixels via `trackRegion` or LLM scoring.
+3) Task agents: capture screen, `llm.plan('increase playback speed to 1.5x')`, vet and execute the plan with safety checks (bounds, foreground window) and reversible steps.
+
+Privacy & safety:
+- Favor on‑device VLMs where possible; redact PII regions; require explicit user consent for cloud processing; log actions when dev mode is on.
+
+
+
+
+## Quick Start
+
+### Check capabilities and request permissions
+
+```ts
+// Feature-detect what the current platform supports
+const caps = await aquery.supports();
+
+// Ask only for what you need right now
+await aquery.requestPermissions({
+ a11y: caps.a11y,
+ input: caps.input,
+ screen: false
+});
+```
+
+### Focus an app window and press a button by accessible name
+
+```ts
+// Bring the target app window forward
+const active = await aquery.window.getActive();
+if (!active) {
+ const safari = (await aquery.app.listInstalled()).find(a => a.name === 'Safari');
+ if (safari) {
+ const wins = await aquery.window.list({ appId: safari.id });
+ if (wins[0]) await aquery.window.activate(wins[0].id);
+ }
+}
+
+// Press a visible OK button
+const ok = await aquery.a11y.query('window > button[name="OK"]:enabled:visible', { timeoutMs: 1000 });
+if (ok[0]) {
+ await aquery.a11y.action('window > button[name="OK"]', 'press');
+}
+```
+
+### Fallback to vision when accessibility lookup fails
+
+```ts
+const match = await aquery.a11y.query('button[name~=/Play|▶/]:enabled', { timeoutMs: 500 });
+if (!match[0] && (await aquery.supports()).screen) {
+ const win = await aquery.window.getActive();
+ if (win) {
+ const img = await aquery.screen.captureWindow(win.id, { format: 'png' });
+ const boxes = await aquery.llm.locate(img, 'play button');
+ const best = boxes.sort((a, b) => b.score - a.score)[0];
+ if (best) {
+ const cx = best.rect.x + Math.floor(best.rect.w / 2);
+ const cy = best.rect.y + Math.floor(best.rect.h / 2);
+ await aquery.input.mouse({ type: 'move', x: cx, y: cy });
+ await aquery.input.mouse({ type: 'click', button: 'left' });
+ }
+ }
+}
+```
+
+---
+
+## Selector Cookbook
+
+- **By role**: `menuitem`, `button`, `textbox`, `checkbox`
+- **By name (exact)**: `button[name="Save"]`
+- **By name (regex)**: `button[name~/^Save (As|All)/]`
+- **By state**: `:enabled`, `:focused`, `:visible`
+- **By ancestry**: `app[name="Safari"] > window > toolbar > button[name="Reload"]`
+- **Any of names**: `button[name~/^(OK|Yes|Continue)$/]`
+- **First match helper**: `a$("button[name='OK']")`
+- **All matches helper**: `a$All("menuitem:visible")`
+
+Tips:
+- Prefer stable identifiers (role + name) first; use vision as a verifier.
+- Scope queries by app/window when possible for performance.
+- Use `timeoutMs` conservatively to avoid long hangs; prefer retries with backoff.
+
+---
+
+## Capability Matrix (indicative)
+
+| Feature | macOS (AX) | Windows (UIA) | Linux X11 (AT‑SPI) | Wayland |
+| ------------- | ---------- | ------------- | ------------------ | ------- |
+| a11y query | Yes | Yes | Yes | Varies |
+| a11y actions | Yes | Yes | Yes | Varies |
+| window list | Yes | Yes | Yes | Yes |
+| window focus | Yes | Yes | Yes | Varies |
+| input synth | Yes | Yes | Yes (XTest/XI2) | Portals |
+| screen capture| Yes | Yes | Yes | Portals |
+
+Notes:
+- Wayland features depend on compositor and portals; expose via `supports()`.
+- Some features require explicit OS permissions; see next section.
+
+---
+
+## Permissions Guide
+
+### macOS
+- **Accessibility**: required for a11y queries/actions and some input simulation.
+- **Input Monitoring**: required for global input hooks.
+- **Screen Recording**: required for display/window capture.
+- Use `aquery.requestPermissions({ a11y: true, input: true })` to guide users.
+
+### Windows
+- UIA and SendInput generally work without special prompts; ensure the app has appropriate privileges when interacting with elevated windows.
+- Graphics Capture may require enabling OS features on older builds.
+
+### Linux
+- **X11**: broad access via AT‑SPI and XTest/XI2; no prompts.
+- **Wayland**: use portals for virtual keyboard/mouse and remote‑desktop capture; availability varies by desktop environment.
+
+---
+
+## Svelte / Browser Polyfill Notes
+
+In `kando-svelte`, mirror the TS surface where possible so code can feature‑detect and degrade gracefully.
+
+```ts
+// Example: simple mouse trigger polyfill in the browser
+const supports = await aquery.supports();
+if (!supports.triggers) {
+ // Fallback: listen to DOM events to open a demo menu
+ window.addEventListener('contextmenu', (e) => {
+ e.preventDefault();
+ // show demo menu component at e.clientX/Y
+ });
+}
+```
+
+Prefer native bridges when running under Electron for capture and input.
+
+---
+
+## Prefab, HyperLook/HyperCard, and Design Inspiration
+
+### Prefab (Dixon & Fogarty)
+- Use pixel‑level recognition to identify widgets and verify targets.
+- Combine with a11y selectors for robust, cross‑app interactions.
+
+### HyperLook / HyperCard‑style Augmentation
+- Treat desktop UIs as canvases you can annotate, overlay, and script.
+- Compose higher‑level widgets (e.g., a generic media controller) that adapt to many apps.
+
+### Window Management
+- Expose predictable operations: focus, move/resize, enumerate, tile/snap.
+- Build user scripts that arrange workspaces and then bind them to triggers.
+
+### Pie Menus
+- Integrate with Kando’s triggers and gesture pipeline.
+- Use aQuery to query context (focused app/window/element) and tailor menu entries.
+
+### Tabbed / Panel Workflows
+- Script workflows that switch tabs, raise panels, and confirm dialogs by role/name.
+
+---
+
+## Eventing, Timeouts, and Cancellation
+
+- Long queries should accept `timeoutMs` and `AbortSignal` to stay responsive.
+- Emit progress or discovery events where supported (future roadmap) to enable live UIs.
+
+```ts
+const controller = new AbortController();
+const timer = setTimeout(() => controller.abort(), 800);
+try {
+ const nodes = await aquery.a11y.query('textbox:focused', { timeoutMs: 750 /*, signal: controller.signal */ });
+ // ...
+} finally {
+ clearTimeout(timer);
+}
+```
+
+---
+
+## Error Handling Patterns
+
+- Always check `supports()` before calling feature APIs.
+- Prefer idempotent scripts; verify window focus and bounds before input.
+- Layer fallbacks: a11y → vision verify → pure vision; fail fast with clear messages.
+
+---
+
+## Contributing Adapters
+
+- Keep platform specifics inside adapter folders; conform to the TS interface.
+- Add contract tests per method and capability matrices per OS.
+- Document any limitations behind `supports()` feature flags.
diff --git a/kando-svelte/notes/button-trigger-support.md b/kando-svelte/notes/button-trigger-support.md
new file mode 100644
index 000000000..7a7898294
--- /dev/null
+++ b/kando-svelte/notes/button-trigger-support.md
@@ -0,0 +1,286 @@
+## Button and Gamepad Trigger Support — Design and Rationale
+
+This document specifies how to add “open menu on button” triggers to Kando, covering mouse buttons and gamepads, with per‑app/window/region conditions, double‑click passthrough for RMB, and tight integration with the existing gesture pipeline (marking, turbo, hover, fixed‑stroke).
+
+Although authored inside `kando-svelte`, the primary scope is the main Kando application (Electron + native backends). The Svelte variant can implement the same TypeScript surface using browser APIs (Gamepad API, DOM pointer events) without native hooks.
+
+### Goals
+- Support opening a menu via:
+ - Keyboard (existing)
+ - Mouse buttons (RMB, MMB, X1/X2, optionally LMB)
+ - Gamepad buttons and optional “press‑tilt‑release” selection flow
+- Reuse existing per‑menu `conditions` (app/window/region) for scoping
+- Offer double‑right‑click passthrough to forward a native RMB click if user cancels quickly
+- Integrate with the gesture pipeline so state machines remain consistent (jitter, dead‑zone, marking/turbo/hover)
+- Cross‑platform shape with platform‑specific implementations
+
+### Non‑Goals
+- Provide global mouse hooks on Wayland (not feasible without compositor/portal support)
+- Replace the existing keyboard shortcut flow (we remain backward‑compatible)
+
+---
+
+## Configuration Model
+
+We keep `shortcut`/`shortcutID` for backwards compatibility and (long‑term) add a unified `triggers` array. Each entry is one of `keyboard | mouse | gamepad`.
+
+Example (JSON excerpt from `menus.json`):
+
+```json
+{
+ "root": { "type": "submenu", "name": "Apps", "icon": "apps", "iconTheme": "material-symbols-rounded" },
+ "shortcut": "Control+Space",
+ "triggers": [
+ { "kind": "keyboard", "shortcut": "Control+Space" },
+ { "kind": "mouse", "button": "right", "mods": [], "when": "matching-conditions", "doubleClickPassthrough": "on-cancel" },
+ { "kind": "gamepad", "button": 0, "stick": "left", "tiltThreshold": 0.35 }
+ ],
+ "conditions": { "appName": "^com.apple.Safari$" }
+}
+```
+
+Schema sketch (TypeScript/Zod intent):
+
+```ts
+type KeyboardTrigger = {
+ kind: 'keyboard';
+ shortcut?: string; // Electron accelerator
+ id?: string; // fallback ID for DE/portal bindings
+};
+
+type MouseTrigger = {
+ kind: 'mouse';
+ button: 'left'|'middle'|'right'|'x1'|'x2';
+ mods?: Array<'ctrl'|'alt'|'shift'|'meta'>;
+ when?: 'matching-conditions'|'always';
+ doubleClickPassthrough?: 'on-cancel'|'never'|'always';
+};
+
+type GamepadTrigger = {
+ kind: 'gamepad';
+ button: number; // W3C remapped indices
+ stick?: 'left'|'right';
+ tiltThreshold?: number; // 0..1, default 0.3
+};
+```
+
+Backward compatibility: if `triggers` is absent, existing `shortcut`/`shortcutID` is treated as a single keyboard trigger.
+
+### Why not piggyback on `shortcut`?
+- Electron’s accelerators are keyboard‑only; mouse buttons are not supported and would require native hooks anyway. Keeping distinct trigger kinds avoids fragile overloading and keeps the editor UX clear.
+
+---
+
+## Update (MVP implemented now): Layered mouse bindings and V1‑compatible field
+
+To ship incremental support without bumping settings version, we added a simple, V1‑compatible field and layered it through the backend so pie menus can use it today, while other features can hook into the same layer later.
+
+- Schema (V1‑compatible):
+ - `menu.mouseBindings: string[]` (default: `[]`)
+ - Strings like `right`, `middle`, `left`, `x1`, `x2` and with modifiers: `ctrl+right`, `alt+right`, `shift+right`, `meta+right`.
+- Editor UI: root menu shows a TagInput for “Mouse bindings” with a tooltip explaining the syntax (no capture UI yet).
+- Backend layering:
+ - macOS native addon exposes `startMouseHook/stopMouseHook` via CGEventTap (listen‑only for now) and emits down/up with button/coords/mods.
+ - The macOS Backend normalizes a binding (e.g., `ctrl+right`) and emits a high‑level `mouseBinding` event. This is intentionally independent from keyboard shortcuts so other subsystems can consume it later (not just pie menus).
+ - The app listens for `mouseBinding` and currently routes it to `showMenu({ trigger: binding })` so menus can bind to these strings.
+- Menu selection matching:
+ - `chooseMenu()` now matches incoming triggers against `shortcut`, `shortcutID`, and `mouseBindings`.
+ - Base‑only fallback: a binding `right` will also match an incoming `ctrl+right`.
+- Robustness / migration: missing `mouseBindings` is treated as `[]` (no version bump).
+- Future: we can switch the tap to intercept mode (swallow RMB) with a passthrough policy; and add non‑menu consumers of `mouseBinding` (e.g., macro layers, context tools).
+
+This MVP aligns with the long‑term `triggers[]` design while remaining backward compatible today.
+
+---
+
+## Integration with Conditions and Menu Selection
+
+We reuse the existing `conditions` matcher (app name, window title, screen region). On any trigger event, Kando computes the “best matching” menu exactly as today. If the selected menu contains a trigger matching the event (kind + details), we open it.
+
+This supports per‑app RMB bindings naturally: put `mouse` triggers on menus and scope them with `conditions`.
+
+---
+
+## Event Flow and State Machines
+
+### Mouse (open flow)
+1) Global hook sees mouse event (e.g., RightDown + modifiers)
+2) Resolve `WMInfo` (app/window/region), pick best menu by `conditions`
+3) If a matching `mouse` trigger exists: swallow the OS event and `showMenu({ centered/anchored/hover })`
+4) Pointer input continues as today (dead‑zone, jitter, marking/turbo/hover)
+
+### Double‑RMB Passthrough
+- Policy `doubleClickPassthrough`:
+ - `on-cancel` (default): if the menu closes “quickly” (≤ system double‑click interval) without a selection, synthesize RMB (Down+Up) and close
+ - `always`: synthesize RMB on quick close regardless of selection
+ - `never`: never synthesize
+
+### Gamepad (open + browse)
+1) Renderer polls Gamepad API (already implemented for in‑menu browsing)
+2) If a configured `gamepad` trigger button becomes down (and optional `tiltThreshold` satisfied), notify main to `showMenu` using current WM info and menu selection rules
+3) In‑menu browsing uses existing GamepadInput: analog stick → hover; buttons → select/back/close
+4) Optional mode: “press‑tilt‑release” — arm on button down, commit selection on button up (setting `gamepadSelectOnButtonUp`)
+
+---
+
+## Native Backends (platform support)
+
+### macOS (first target)
+- Global capture: CGEventTap (kCGHIDEventTap) for Right/Other buttons, with Accessibility permission
+- Swallowing: return `nullptr` to prevent OS delivery when opening Kando
+- Synthetic events: `CGEventCreateMouseEvent` + `CGEventPost`, support: move, down, up, click, dblclick (set click state), scroll, with modifiers
+- Permissions: reuse current accessibility prompt; show guidance if access denied
+
+### Windows (next)
+- Global capture: `SetWindowsHookEx(WH_MOUSE_LL)`
+- Synthetic events: `SendInput` for mouse
+
+### Linux
+- X11: XI2 + XTest (best effort)
+- Wayland: no global mouse hooks; disable mouse triggers and recommend DE/portal bindings (keyboard only)
+
+---
+
+## Public Native API (cross‑platform shape)
+
+- `startMouseHook({ buttons: string[], intercept: boolean }): void`
+- `stopMouseHook(): void`
+- `simulateMouse(event: {
+ type: 'move'|'down'|'up'|'click'|'dblclick'|'scroll',
+ button?: 'left'|'middle'|'right'|'x1'|'x2',
+ x?: number, y?: number, dx?: number, dy?: number,
+ scrollX?: number, scrollY?: number,
+ modifiers?: { shift?: boolean, ctrl?: boolean, alt?: boolean, meta?: boolean }
+ }): void`
+- Emits events: `{ button, phase: 'down'|'up', x, y, mods, timestamp }`
+
+These APIs are implemented natively per OS but identically shaped in Node.
+
+---
+
+## Active‑Window‑Aware Filtering (tap/untap strategy)
+
+We minimize overhead and avoid “spying” on clicks in non‑target apps by enabling the intercepting hook only when necessary:
+
+- Maintain a precomputed index of triggers per app/window pattern (compiled regex), plus any `when: 'always'` triggers.
+- Track foreground window changes and pointer screen transitions; when the active app/window does not match any mouse triggers, disable the intercepting hook (or switch to a listen‑only tap where available). When a match appears, enable the intercepting hook.
+- On platforms where toggling hooks is cheap, fully stop/start; otherwise `enable/disable` the same handle.
+
+Platform specifics:
+- macOS: subscribe to `NSWorkspaceDidActivateApplicationNotification` to detect app changes; `CGEventTapEnable(tap, true|false)` to toggle; use `kCGEventTapOptionListenOnly` when you want metrics without the ability to swallow. On match, keep the tap enabled in intercept mode; otherwise disable or switch to listen‑only.
+- Windows: use `SetWinEventHook(EVENT_SYSTEM_FOREGROUND, ...)` to detect focus changes; toggle `WH_MOUSE_LL` hook accordingly.
+- X11: watch `_NET_ACTIVE_WINDOW` via `XSelectInput` and PropertyNotify; toggle XI2 hook.
+- Wayland: no reliable foreground app events; default to disabled hooks (mouse triggers unsupported) or to a per‑DE integration if available.
+
+Race considerations:
+- For RMB interception you must already be in intercept mode before the actual `RightDown` is dispatched by the OS. Therefore we only disable interception in apps without matching triggers; in apps with matches, interception remains enabled and the event is swallowed conditionally (constant‑time checks).
+- Region conditions: decision is per‑event (we read pointer position from the event); interception remains enabled in candidate apps.
+
+Behavior summary:
+- Not a candidate app/window → hook disabled (zero overhead).
+- Candidate app/window → hook enabled, events checked against triggers; if not matched, immediately pass through; if matched, swallow and open menu.
+
+---
+
+## LLM‑Driven Context‑Sensitive Menus (window snapshot → pie)
+
+We can dynamically propose a menu when a trigger fires by analyzing the active window snapshot or accessibility tree. This augments (not replaces) authored menus.
+
+High‑level pipeline:
+1) Capture: use `aquery.screen.captureWindow(activeWindowId)` (prefer GPU/zero‑copy). Optionally include `aquery.a11y.query('window > *')` summaries.
+2) Prompt: send snapshot (and AX summary) to `aquery.llm.describe/plan` with an instruction to emit a Kando `menus.json` fragment limited to 8–12 directions, names/icons, and safe actions only.
+3) Validate: parse with Kando Zod schemas; reject if invalid or if contains disallowed actions.
+4) Render: open the generated pie (ephemeral) or merge into a temporary overlay group; show a small “AI” badge and a “pin/save” affordance.
+5) Learn/cache: key by app/window signature (bundle id + canonicalized title + UI hash). Cache top suggestions; allow feedback (👍/👎) and corrections; respect per‑app opt‑in.
+
+Output schema (LLM target):
+```json
+{
+ "version": "1",
+ "menus": [
+ {
+ "name": "AI Context",
+ "centered": false,
+ "anchored": false,
+ "root": {
+ "type": "submenu",
+ "name": "Context",
+ "icon": "lightbulb",
+ "iconTheme": "material-symbols-rounded",
+ "children": [
+ { "type": "hotkey", "name": "Copy", "icon": "content_copy", "iconTheme": "material-symbols-rounded", "data": { "hotkey": "Command+C" } },
+ { "type": "hotkey", "name": "Paste", "icon": "content_paste", "iconTheme": "material-symbols-rounded", "data": { "hotkey": "Command+V" } }
+ ]
+ }
+ }
+ ]
+}
+```
+
+Safety & UX guardrails:
+- Privacy: default to on‑device VLM; if cloud is used, require explicit per‑app consent and allow redaction regions.
+- Safety: only emit Kando‑supported safe actions (`hotkey`, `command`, `uri`, etc.); require confirmation for destructive actions.
+- Determinism: clamp to ≤12 slices; prefer well‑known icons; avoid ambiguous labels; show confidence tooltips.
+- Latency: if the LLM response exceeds a threshold, show the default authored menu first and add AI suggestions as a sibling pie when ready.
+
+Refresh policy:
+- Recompute when window identity or major layout hash changes.
+- Cache per app/title signature; decay over time; respect user feedback.
+
+Integration points:
+- Triggers: `MouseTrigger`/`GamepadTrigger` can select an AI pie variant when `conditions` match and AI is enabled for the app.
+- Editor: provide a “Generate with AI” button that seeds a baseline menu the user can edit and save.
+
+---
+
+## Editor UI Changes
+
+- Keyboard: keep `ShortcutPicker` (existing)
+- Mouse: add `MouseTriggerPicker`
+ - Record button captures `mousedown` inside the dialog (button + current modifiers)
+ - Options: When (`matching-conditions|always`), Passthrough (`on-cancel|never|always`)
+- Gamepad: add `GamepadTriggerPicker`
+ - Record listens via Gamepad API; captures button index; optional tilt threshold slider; stick selector
+
+All pickers edit a `triggers[]` list on the menu.
+
+---
+
+## Gesture Pipeline Integration
+
+The menu opens in the same state machine as keyboard triggers; thereafter pointer/gamepad input is handled by the existing InputMethods.
+
+- PointerInput: unchanged for motion/jitter/marking/turbo/hover. Only the open event origin differs.
+- GestureDetector: remains authoritative for corner/pause detection and fixed‑stroke; nothing changes here.
+- GamepadInput: continues to publish an `InputState` with `distance/angle` based on stick tilt; optional “select on button up” adds a small arm/disarm flag in the input method.
+
+Fast gesture modes (e.g., `fixedStrokeLength`) continue to apply; if configured, a gamepad tilt beyond threshold may immediately select upon button release if distance crosses the fixed stroke.
+
+---
+
+## Svelte Variant (browser)
+
+Svelte apps can mirror the same TypeScript model without native code:
+- Mouse triggers: use `pointerdown`/`contextmenu` on a global overlay to detect RMB/MMB; browsers allow canceling the default context menu
+- Gamepad triggers: use the browser Gamepad API (as in Kando renderer) for both opening and browsing
+
+The Svelte implementation should parse the same `triggers` array and apply identical selection/gesture logic, differing only in the capture layer.
+
+---
+
+## Telemetry, Testing, and Migration
+
+- Logging: emit concise lines when a trigger matches or is ignored (kind, button/index, app/window, chosen menu)
+- Unit tests: Zod schema for `triggers`; condition matcher remains as is
+- Manual tests: per‑app RMB, double‑RMB passthrough, gamepad open/tilt/select, fixed‑stroke interactions
+- Migration: if `triggers` missing, build a single `keyboard` trigger from `shortcut`/`shortcutID` at load time; editor writes the new schema going forward
+
+---
+
+## Rationale Summary
+
+- Distinct trigger kinds keep platform realities clear (keyboard via Electron/portals; mouse via native hooks; gamepad via web API) while sharing the same conditions and open/gesture pipeline.
+- Double‑RMB passthrough preserves native app context menus without spending a slice.
+- The unified `triggers[]` is backward‑compatible and future‑proof (room for touch/pen or OS‑level gestures later).
+
+
diff --git a/kando-svelte/notes/dom-css-compatibility.md b/kando-svelte/notes/dom-css-compatibility.md
new file mode 100644
index 000000000..efbbffc67
--- /dev/null
+++ b/kando-svelte/notes/dom-css-compatibility.md
@@ -0,0 +1,665 @@
+# Exhaustive DOM/CSS compatibility report (Svelte vs Kando)
+
+This document captures the exact DOM/CSS contract Kando uses and the differences observed in kando‑svelte, together with concrete fixes. The goal is 100% compatibility: same DOM structure, same CSS variables, same classes, and the same behavior across themes.
+
+## 1) High‑level structure
+
+- Kando
+ - Single nested tree. One `div.menu-node.level-0` (the parent/center) contains all descendants.
+ - The selected child that becomes the new center is still a descendant of the parent and is positioned via a relative translate inside the parent node.
+- Current Svelte
+ - Two parallel trees rendered as separate siblings: one `.pie-level` for the parent preview and another `.pie-level` for the active tip level.
+ - Both centers are absolutely translated to the same screen center, instead of nesting the active center under the parent.
+- Fix
+ - Render a single `PieMenu` tree. If you keep a parent preview, its active child MUST be an immediate child of the parent `menu-node` with a relative `translate(...)` equal to child distance in the selected direction. Avoid a second top‑level `.pie-level` for the active level.
+
+## 2) Center positioning
+
+- Kando
+ - Parent center: `transform: translate(, )` on `.menu-node.level-0.parent`.
+ - Active submenu center: `transform: translate(dx, dy)` on `.menu-node.level-1.active`, where `dx/dy = max(--child-distance, 10px * var(--sibling-count)) * (--dir-x/--dir-y)`. Absolute center = parent absolute + relative.
+- Current Svelte
+ - Both parent and tip `.menu-node.level-0` receive the same absolute translate.
+ - The active center is not a relative child of the parent; child placement and connectors misalign and “slide”.
+- Fix
+ - Keep the parent `.menu-node.level-0` translated to absolute center. Insert the active child `.menu-node.level-1.active` as its descendant with relative translate only. Do not re‑translate a second root.
+
+## 3) DOM nesting and levels
+
+- Kando: `.menu-node.level-0.parent` contains `.menu-node.level-1.*` children; each level‑1 submenu contains `.menu-node.level-2.*` grandchildren.
+- Current Svelte: the top‑level “tip” `.menu-node.level-0.active` contains `.level-1.*`, while a separate `.level-0.parent` exists alongside it.
+- Fix: ensure the “active” level (and its grandchildren) are descendants of the parent node, not siblings in a different top‑level container.
+
+## 4) Parent/child bars, gaps and “nubs”
+
+- Kando: parent’s `.connector` is drawn inside `.menu-node.level-0.parent` with a width equal to the active child distance and rotated to connect to that child. The active child sits at the connector end.
+- Current Svelte: parent and active levels are disconnected; connectors are computed relative to duplicate centers; bars and nubs don’t align.
+- Fix: compute the parent connector width/rotation inside the parent node (only) and position the active child as a descendant of the parent. Stop drawing connectors in a duplicate “tip” tree.
+
+## 5) Relative transforms and CSS contract
+
+- Kando: children/grandchildren transform via CSS only using `--dir-x/--dir-y` and theme distances:
+ - Child: `translate(calc(max(var(--child-distance), 10px * var(--sibling-count)) * var(--dir-x)), calc(... * var(--dir-y)))`.
+ - Grandchild: `translate(var(--grandchild-distance) * var(--dir-x), var(--grandchild-distance) * var(--dir-y))`.
+ - JS sets inline transforms only for dragged/clicked items.
+- Current Svelte: correct vars exist but the wrong ancestor breaks relative positioning.
+- Fix: keep children/grandchildren in the correct parent; do NOT inline transforms on children (except drag/click); let theme CSS position them.
+
+## 6) Active submenu center transform
+
+- Kando: `.menu-node.level-1.type-submenu.active` has a relative `translate(dx, dy)` under the parent (e.g., `translate(≈0px, -150px)` for a top child).
+- Current Svelte: no `.level-1.active` relative translate under the parent; a second `.level-0.active` is used instead.
+- Fix: produce `.menu-node.level-1.active` under the parent with relative translate per theme CSS rules.
+
+## 7) `--parent-angle` propagation for grandchildren
+
+- Kando: grandchildren `.level-2` nodes carry `--parent-angle: deg` to orient wedges/gaps.
+- Current Svelte: sets `--parent-angle`, but grandchildren live under a separate root so visuals appear in the wrong place.
+- Fix: ensure grandchildren are descendants of the selected child (itself a descendant of the parent) so `--parent-angle` works as intended.
+
+## 8) Selection wedges and separators
+
+- Kando: global singletons sized to the viewport; separators: `translate(centerX, centerY) rotate(angle-90deg)`; wedges read `--center-x/--center-y`.
+- Current Svelte: components added and translated correctly but must be driven by the same center as the single nested tree.
+- Fix: maintain one instance of each overlay, recomputed when chain/hover changes.
+
+## 9) Center text
+
+- Kando: `center-text` is absolutely positioned with `translate(centerX, centerY)` and uses deferred measurement to vertically center text in the circle; cached for performance.
+- Current Svelte: placeholder; placed within the active node without iterative layout.
+- Fix: replicate `center-text.ts` (deferred measurement + caching) and absolutely translate to the active center.
+
+## 10) Icon font tag
+
+- Kando: `glyph` and `` in `.icon-container`.
+- Current Svelte: updated to `` (good). Keep consistent.
+
+## 11) Directional classes
+
+- Kando: `.left/.right/.top/.bottom` based on `--dir-x/--dir-y` thresholds.
+- Fix: keep generating these; correctness depends on proper nesting.
+
+## 12) Connector rotation accumulation
+
+- Kando: avoids 360° flips by tracking `lastConnectorAngle` and using closest‑equivalent rotation.
+- Current Svelte: connector angle recomputed directly; can flip.
+- Fix: store/accumulate connector rotation per item using closest‑equivalent logic.
+
+## 13) Duplicate centers and the “sliding” bug
+
+- Root cause: a second absolute root is rendered for the active level; the UI slides instead of nesting.
+- Fix: use one root; move it once when needed; nest the active center under the parent.
+
+---
+
+## Concrete implementation changes (Svelte)
+
+### `PieTree.svelte`
+- Render a single nested tree (remove separate tip tree).
+- On selection, compute the new root center per Kando’s `selectItem()`; then nest the active child relative to the parent.
+- Keep a single global wedges/separators overlay driven by the same center.
+
+### `PieMenu.svelte`
+- Stop creating a second `.level-0.active` root.
+- Render `.level-1.active` as a child of the parent node; children/grandchildren position via CSS vars only.
+- Set `--parent-angle` for grandchildren; compute parent connector width/rotation in parent; use closest‑equivalent rotation.
+
+### `PieItem.svelte`
+- Maintain `` icon tags; set CSS vars and directional classes; avoid inline transforms (except drag/click).
+
+### `SelectionWedges.svelte` / `WedgeSeparators.svelte`
+- Singletons; `translate(center.x, center.y) rotate(angle-90deg)` for separators; wedges read `--center-x/--center-y` and hovered wedge angles.
+
+### `CenterText.svelte`
+- Implement iterative layout and caching; translate to active center; mirror Kando behavior.
+
+---
+
+## Key acceptance checks
+
+- One `.menu-node.level-0.parent` at absolute center containing the entire tree.
+- After selecting a submenu, the active `.menu-node.level-1.active` is a descendant with correct relative translate.
+- Parent connector terminates exactly at the active child; bars/nubs align; wedges/separators rotate correctly.
+- Direction classes, CSS vars, and name/icon layers behave identically across Kando’s themes.
+
+---
+
+## Kando Goal HTML and CSS outerHTML (Apps submenu selected)
+
+```html
+
+
+
+
+
+
+ music_note
+
+
+
+
+
+
+ home
+
+
+
+
+
+
+ desktop_windows
+
+
+
+
+
+
+ text_ad
+
+
+
+
+
+
+ imagesmode
+
+
+
+
+
+
+ video_camera_front
+
+
+
+
+
+
+ download
+
+
+
+
+
+ folder_special
+
+
+
+
+
+
+
+
+ arrow_back
+
+
+
+
+
+
+
+
+ text_select_jump_to_beginning
+
+
+
+
+
+
+ cancel_presentation
+
+
+
+
+
+
+ text_select_jump_to_end
+
+
+
+
+
+
+ select_window
+
+
+
+
+
+ select_window
+
+
+
+
+
+
+
+
+
+ skip_previous
+
+
+
+
+
+
+ play_pause
+
+
+
+
+
+
+ skip_next
+
+
+
+
+
+ play_circle
+
+
+
+
+
+
+
+
+
+ cut
+
+
+
+
+
+
+ content_copy
+
+
+
+
+
+
+ content_paste_go
+
+
+
+
+
+ assignment
+
+
+
+
+
+
+
+
+ arrow_forward
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ public
+
+
+
+
+
+
+
+
+
+ terminal
+
+
+
+
+
+
+ folder_shared
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mail
+
+
+
+
+
+
+
+
+
+
+
+
+ apps
+
+
+
+
+
+
+
+ award_star
+
+
+
+
+
+
E-Mail
+
+```
+
+---
+
+## Kando DOM/CSS and Protocol Contracts (Authoritative)
+
+This section defines the target contract the Svelte implementation MUST match exactly.
+
+### A. Layering and Structure
+- One overlay root per popup with two logical layers:
+ - Parent layer: `.menu-node.level-0.parent` positioned at absolute center using `transform: translate(, )`.
+ - Active layer: `.menu-node.level-0.active` positioned at the same absolute center. Its `.level-1` children are rendered here.
+- Grandchild previews ("nubs") are rendered in the parent layer under each `.level-1` child that has children, as `.menu-node.level-2.grandchild` elements. They are not separate menus.
+- No duplicate absolute roots other than the two centers above.
+
+### B. Class names and dataset attributes
+- Every node is `.menu-node level- type- [parent|active|child|grandchild] [left|right|top|bottom]`.
+- Required data attributes for tooling and themes:
+ - `data-name`, `data-type`, `data-path`, `data-level`.
+- Directional classes derive from `--dir-x/--dir-y` thresholds exactly like Kando.
+
+### C. Inline styles and CSS custom properties
+- Inline style variables are authoritative inputs for theme CSS:
+ - `--dir-x`, `--dir-y`: unitless direction cosines for each item.
+ - `--angle`: item’s absolute direction in degrees.
+ - `--sibling-count`: number of siblings at that level.
+ - `--child-distance`: pixel radius for level‑1 children; set on the center node.
+ - `--parent-angle`: on grandchildren, equal to the angle of their parent (the selected child) relative to its parent center.
+ - Optional: `--angle-diff` for themes that need delta from pointer.
+- Center nodes apply absolute `transform: translate(centerX, centerY)` only. Children and grandchildren must not receive inline `transform` from JS (except transient drag/press effects). Their placement is driven by CSS using the variables above.
+
+### D. Child placement (CSS-driven, no JS transforms)
+- Level‑1 children translation relative to center:
+ - `translate(calc(max(var(--child-distance), 10px * var(--sibling-count)) * var(--dir-x)), calc(max(var(--child-distance), 10px * var(--sibling-count)) * var(--dir-y)))`.
+- Level‑2 grandchildren translation relative to their child:
+ - `translate(calc(var(--grandchild-distance) * var(--dir-x)), calc(var(--grandchild-distance) * var(--dir-y)))`.
+- Themes define `--grandchild-distance`; core code only sets `--parent-angle` for wedge alignment.
+
+### E. Connectors and gaps
+- The parent layer draws a single `.connector` inside `.menu-node.level-0.parent`:
+ - Width equals distance from parent center to the selected child’s center.
+ - Rotation equals `angle(parent→child) - 90deg`, accumulated with closest‑equivalent logic to avoid flips.
+- The active layer draws its own `.connector` sized to `--child-distance` and rotated to the hovered child; it may be zero width.
+- Gaps and “nubs” are entirely CSS-driven via `--parent-angle` on descendants.
+
+### F. Layers content and ordering
+- Theme layers follow Kando’s `MenuThemeDescription.layers` contract. Typical:
+ - `.icon-layer[data-content="icon"]` (required for icon glyphs)
+ - `.label-layer[data-content="name"]` (optional; theme decides visibility and style)
+- Icons render inside `.icon-container` as:
+ - `{glyph}` or ``
+ - SVG `` allowed for packs; must live inside `.icon-container`.
+- A visually hidden `