@sirenodeck/sirenodeck 0.1.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/bin/_wrapper-shared.js +42 -0
- package/bin/dev.js +95 -0
- package/bin/sirenodeck.js +42 -0
- package/config/default-config.yml +117 -0
- package/config/starter-config.yml +54 -0
- package/dist/main.mjs +64702 -0
- package/package.json +140 -0
- package/src/action/executor.ts +156 -0
- package/src/action/index.ts +7 -0
- package/src/addon/api-types.ts +15 -0
- package/src/addon/api.ts +461 -0
- package/src/addon/check-runner.ts +84 -0
- package/src/addon/index.ts +5 -0
- package/src/addon/loader.ts +441 -0
- package/src/addon/registry.ts +230 -0
- package/src/addon/spec.ts +69 -0
- package/src/api/addon.ts +33 -0
- package/src/api/asset-url.ts +24 -0
- package/src/api/index.ts +50 -0
- package/src/api/protocol-internal.ts +656 -0
- package/src/api/protocol.ts +2 -0
- package/src/api/react/index.ts +11 -0
- package/src/api/react/registry.ts +91 -0
- package/src/api/react/use-addon-channel.ts +26 -0
- package/src/api/react/use-button-state.tsx +23 -0
- package/src/api/react/use-deck.ts +24 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/logo72x72.png +0 -0
- package/src/assets/logoFull.png +0 -0
- package/src/builtin-addons/brightness/README.md +51 -0
- package/src/builtin-addons/brightness/buttons/brightness/backend.ts +13 -0
- package/src/builtin-addons/brightness/buttons/brightness/config.ts +10 -0
- package/src/builtin-addons/brightness/buttons/brightness/frontend.tsx +50 -0
- package/src/builtin-addons/brightness/domain/macos.ts +46 -0
- package/src/builtin-addons/brightness/index.d.ts +5 -0
- package/src/builtin-addons/brightness/index.ts +20 -0
- package/src/builtin-addons/brightness/poller.ts +27 -0
- package/src/builtin-addons/brightness/providers/darwin.ts +107 -0
- package/src/builtin-addons/brightness/providers/index.ts +81 -0
- package/src/builtin-addons/brightness/providers/linux.ts +108 -0
- package/src/builtin-addons/brightness/providers/windows.ts +77 -0
- package/src/builtin-addons/brightness/sirenodeck.json +6 -0
- package/src/builtin-addons/coding-agents/assets/claude-code.svg +1 -0
- package/src/builtin-addons/coding-agents/assets/opencode-dark-square.svg +1 -0
- package/src/builtin-addons/coding-agents/buttons/agent/backend.ts +82 -0
- package/src/builtin-addons/coding-agents/buttons/agent/config.ts +13 -0
- package/src/builtin-addons/coding-agents/buttons/agent/frontend.tsx +130 -0
- package/src/builtin-addons/coding-agents/buttons/agent/pages.tsx +150 -0
- package/src/builtin-addons/coding-agents/buttons/summary/backend.ts +48 -0
- package/src/builtin-addons/coding-agents/buttons/summary/config.ts +11 -0
- package/src/builtin-addons/coding-agents/buttons/summary/frontend.tsx +254 -0
- package/src/builtin-addons/coding-agents/decks/agents.ts +69 -0
- package/src/builtin-addons/coding-agents/global/backend.ts +402 -0
- package/src/builtin-addons/coding-agents/global-entry.ts +7 -0
- package/src/builtin-addons/coding-agents/hook/script.ts +123 -0
- package/src/builtin-addons/coding-agents/index.ts +15 -0
- package/src/builtin-addons/coding-agents/manifest.ts +115 -0
- package/src/builtin-addons/coding-agents/providers/claude-code.ts +470 -0
- package/src/builtin-addons/coding-agents/providers/claude-instances.ts +184 -0
- package/src/builtin-addons/coding-agents/providers/opencode-instances.ts +108 -0
- package/src/builtin-addons/coding-agents/providers/opencode.ts +636 -0
- package/src/builtin-addons/coding-agents/providers/registry.ts +120 -0
- package/src/builtin-addons/coding-agents/providers/spawn.ts +112 -0
- package/src/builtin-addons/coding-agents/shared/claude-hook-events.ts +55 -0
- package/src/builtin-addons/coding-agents/shared/claude-status.ts +236 -0
- package/src/builtin-addons/coding-agents/shared/live-count.ts +39 -0
- package/src/builtin-addons/coding-agents/shared/notifier.ts +58 -0
- package/src/builtin-addons/coding-agents/shared/opencode-status.ts +82 -0
- package/src/builtin-addons/coding-agents/shared/snapshot.ts +66 -0
- package/src/builtin-addons/coding-agents/shared/state.ts +72 -0
- package/src/builtin-addons/coding-agents/sirenodeck.json +6 -0
- package/src/builtin-addons/coding-agents/types/types.ts +33 -0
- package/src/builtin-addons/core/README.md +89 -0
- package/src/builtin-addons/core/buttons/action/backend.ts +7 -0
- package/src/builtin-addons/core/buttons/action/config.ts +13 -0
- package/src/builtin-addons/core/buttons/action/frontend.tsx +16 -0
- package/src/builtin-addons/core/buttons/change-deck/backend.ts +18 -0
- package/src/builtin-addons/core/buttons/change-deck/config.ts +15 -0
- package/src/builtin-addons/core/buttons/change-deck/frontend.tsx +18 -0
- package/src/builtin-addons/core/buttons/media-sample/backend.ts +8 -0
- package/src/builtin-addons/core/buttons/media-sample/config.ts +7 -0
- package/src/builtin-addons/core/buttons/media-sample/frontend.tsx +2 -0
- package/src/builtin-addons/core/buttons/page-nav/backend.ts +28 -0
- package/src/builtin-addons/core/buttons/page-nav/config.ts +9 -0
- package/src/builtin-addons/core/buttons/page-nav/frontend.tsx +68 -0
- package/src/builtin-addons/core/buttons/toggle/backend.ts +119 -0
- package/src/builtin-addons/core/buttons/toggle/config.ts +57 -0
- package/src/builtin-addons/core/buttons/toggle/frontend.tsx +57 -0
- package/src/builtin-addons/core/buttons/toggle/global-service.ts +174 -0
- package/src/builtin-addons/core/index.ts +120 -0
- package/src/builtin-addons/core/sirenodeck.json +6 -0
- package/src/builtin-addons/date-time/README.md +73 -0
- package/src/builtin-addons/date-time/buttons/analog-clock/backend.ts +10 -0
- package/src/builtin-addons/date-time/buttons/analog-clock/config.ts +4 -0
- package/src/builtin-addons/date-time/buttons/analog-clock/frontend.tsx +88 -0
- package/src/builtin-addons/date-time/buttons/custom/backend.ts +10 -0
- package/src/builtin-addons/date-time/buttons/custom/config.ts +9 -0
- package/src/builtin-addons/date-time/buttons/custom/frontend.tsx +26 -0
- package/src/builtin-addons/date-time/buttons/date/backend.ts +10 -0
- package/src/builtin-addons/date-time/buttons/date/config.ts +10 -0
- package/src/builtin-addons/date-time/buttons/date/frontend.tsx +42 -0
- package/src/builtin-addons/date-time/buttons/time/backend.ts +10 -0
- package/src/builtin-addons/date-time/buttons/time/config.ts +9 -0
- package/src/builtin-addons/date-time/buttons/time/frontend.tsx +29 -0
- package/src/builtin-addons/date-time/index.d.ts +5 -0
- package/src/builtin-addons/date-time/index.ts +30 -0
- package/src/builtin-addons/date-time/poller.ts +11 -0
- package/src/builtin-addons/date-time/shared/format-date.ts +36 -0
- package/src/builtin-addons/date-time/shared/format.ts +105 -0
- package/src/builtin-addons/date-time/shared/use-now.ts +10 -0
- package/src/builtin-addons/date-time/sirenodeck.json +6 -0
- package/src/builtin-addons/emoji-selector/README.md +45 -0
- package/src/builtin-addons/emoji-selector/assets/launcher.svg +10 -0
- package/src/builtin-addons/emoji-selector/assets/smileys.svg +8 -0
- package/src/builtin-addons/emoji-selector/buttons/category/backend.ts +14 -0
- package/src/builtin-addons/emoji-selector/buttons/category/config.ts +8 -0
- package/src/builtin-addons/emoji-selector/buttons/category/frontend.tsx +19 -0
- package/src/builtin-addons/emoji-selector/buttons/emoji/backend.ts +7 -0
- package/src/builtin-addons/emoji-selector/buttons/emoji/config.ts +9 -0
- package/src/builtin-addons/emoji-selector/buttons/emoji/frontend.tsx +11 -0
- package/src/builtin-addons/emoji-selector/buttons/launcher/backend.ts +13 -0
- package/src/builtin-addons/emoji-selector/buttons/launcher/config.ts +6 -0
- package/src/builtin-addons/emoji-selector/buttons/launcher/frontend.tsx +31 -0
- package/src/builtin-addons/emoji-selector/data/categories.ts +1084 -0
- package/src/builtin-addons/emoji-selector/decks/index.ts +137 -0
- package/src/builtin-addons/emoji-selector/index.d.ts +5 -0
- package/src/builtin-addons/emoji-selector/index.ts +29 -0
- package/src/builtin-addons/emoji-selector/sirenodeck.json +6 -0
- package/src/builtin-addons/emoji-selector/support.ts +49 -0
- package/src/builtin-addons/index.ts +13 -0
- package/src/builtin-addons/internal-settings/README.md +26 -0
- package/src/builtin-addons/internal-settings/backend.ts +58 -0
- package/src/builtin-addons/internal-settings/buttons/app-info/backend.ts +7 -0
- package/src/builtin-addons/internal-settings/buttons/app-info/config.ts +13 -0
- package/src/builtin-addons/internal-settings/buttons/app-info/frontend.tsx +18 -0
- package/src/builtin-addons/internal-settings/buttons/brightness/common/frontend.tsx +50 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-down/backend.ts +16 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-down/config.ts +6 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-down/frontend.tsx +8 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-up/backend.ts +16 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-up/config.ts +6 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-up/frontend.tsx +8 -0
- package/src/builtin-addons/internal-settings/decks/settings.ts +25 -0
- package/src/builtin-addons/internal-settings/index.d.ts +4 -0
- package/src/builtin-addons/internal-settings/index.ts +53 -0
- package/src/builtin-addons/internal-settings/logo72x72.png +0 -0
- package/src/builtin-addons/internal-settings/sirenodeck.json +6 -0
- package/src/builtin-addons/media/README.md +45 -0
- package/src/builtin-addons/media/backend.ts +143 -0
- package/src/builtin-addons/media/buttons/media-player/backend.ts +19 -0
- package/src/builtin-addons/media/buttons/media-player/components/MediaStatus.tsx +25 -0
- package/src/builtin-addons/media/buttons/media-player/components/MediaSurface.tsx +52 -0
- package/src/builtin-addons/media/buttons/media-player/components/ProgressBar.tsx +21 -0
- package/src/builtin-addons/media/buttons/media-player/components/status-meta.ts +36 -0
- package/src/builtin-addons/media/buttons/media-player/config.ts +4 -0
- package/src/builtin-addons/media/buttons/media-player/frontend.tsx +29 -0
- package/src/builtin-addons/media/buttons/volume/common/frontend.tsx +52 -0
- package/src/builtin-addons/media/buttons/volume/mute/backend.ts +13 -0
- package/src/builtin-addons/media/buttons/volume/mute/config.ts +4 -0
- package/src/builtin-addons/media/buttons/volume/mute/frontend.tsx +26 -0
- package/src/builtin-addons/media/buttons/volume/volume-down/backend.ts +14 -0
- package/src/builtin-addons/media/buttons/volume/volume-down/config.ts +6 -0
- package/src/builtin-addons/media/buttons/volume/volume-down/frontend.tsx +10 -0
- package/src/builtin-addons/media/buttons/volume/volume-up/backend.ts +14 -0
- package/src/builtin-addons/media/buttons/volume/volume-up/config.ts +6 -0
- package/src/builtin-addons/media/buttons/volume/volume-up/frontend.tsx +8 -0
- package/src/builtin-addons/media/checks.ts +104 -0
- package/src/builtin-addons/media/executor.ts +36 -0
- package/src/builtin-addons/media/index.d.ts +5 -0
- package/src/builtin-addons/media/index.ts +53 -0
- package/src/builtin-addons/media/progress.ts +16 -0
- package/src/builtin-addons/media/providers/darwin.ts +391 -0
- package/src/builtin-addons/media/providers/index.ts +27 -0
- package/src/builtin-addons/media/providers/linux.ts +196 -0
- package/src/builtin-addons/media/providers/types.ts +48 -0
- package/src/builtin-addons/media/providers/windows.ts +121 -0
- package/src/builtin-addons/media/sirenodeck.json +6 -0
- package/src/builtin-addons/media/state.ts +19 -0
- package/src/builtin-addons/register-builtins.ts +65 -0
- package/src/builtin-addons/session/README.md +14 -0
- package/src/builtin-addons/session/buttons/session-info/backend.ts +8 -0
- package/src/builtin-addons/session/buttons/session-info/config.ts +4 -0
- package/src/builtin-addons/session/buttons/session-info/frontend.tsx +2 -0
- package/src/builtin-addons/session/buttons/time/backend.ts +7 -0
- package/src/builtin-addons/session/buttons/time/config.ts +6 -0
- package/src/builtin-addons/session/buttons/time/frontend.tsx +2 -0
- package/src/builtin-addons/session/index.d.ts +5 -0
- package/src/builtin-addons/session/index.ts +18 -0
- package/src/builtin-addons/session/sirenodeck.json +6 -0
- package/src/builtin-addons/system-status/README.md +169 -0
- package/src/builtin-addons/system-status/buttons/_shared.ts +131 -0
- package/src/builtin-addons/system-status/buttons/system-status/frontend.tsx +224 -0
- package/src/builtin-addons/system-status/buttons/system-status/schemas.ts +67 -0
- package/src/builtin-addons/system-status/domain/catalog.ts +53 -0
- package/src/builtin-addons/system-status/domain/chart-sampler.ts +58 -0
- package/src/builtin-addons/system-status/domain/display-metrics.ts +128 -0
- package/src/builtin-addons/system-status/domain/index.ts +10 -0
- package/src/builtin-addons/system-status/domain/live-metrics.ts +1021 -0
- package/src/builtin-addons/system-status/domain/metric-ids.ts +0 -0
- package/src/builtin-addons/system-status/index.ts +16 -0
- package/src/builtin-addons/system-status/manifest.ts +105 -0
- package/src/builtin-addons/system-status/shared/metrics-catalog.ts +316 -0
- package/src/builtin-addons/system-status/sirenodeck.json +6 -0
- package/src/builtin-addons/test-buildin/decks/action-buttons-test.ts +56 -0
- package/src/builtin-addons/test-buildin/index.d.ts +5 -0
- package/src/builtin-addons/test-buildin/index.ts +16 -0
- package/src/builtin-addons/value-display/README.md +59 -0
- package/src/builtin-addons/value-display/backend.ts +86 -0
- package/src/builtin-addons/value-display/buttons/value-display/backend.ts +17 -0
- package/src/builtin-addons/value-display/buttons/value-display/config.ts +36 -0
- package/src/builtin-addons/value-display/buttons/value-display/frontend.tsx +54 -0
- package/src/builtin-addons/value-display/index.d.ts +5 -0
- package/src/builtin-addons/value-display/index.ts +23 -0
- package/src/builtin-addons/value-display/sirenodeck.json +6 -0
- package/src/builtin-addons/weather/README.md +46 -0
- package/src/builtin-addons/weather/backend.ts +121 -0
- package/src/builtin-addons/weather/buttons/weather/backend.ts +27 -0
- package/src/builtin-addons/weather/buttons/weather/config.ts +29 -0
- package/src/builtin-addons/weather/buttons/weather/frontend.tsx +63 -0
- package/src/builtin-addons/weather/buttons/weather/icons.tsx +88 -0
- package/src/builtin-addons/weather/buttons/weather/pages.tsx +109 -0
- package/src/builtin-addons/weather/domain/codes.ts +35 -0
- package/src/builtin-addons/weather/domain/fetch.ts +47 -0
- package/src/builtin-addons/weather/index.d.ts +5 -0
- package/src/builtin-addons/weather/index.ts +23 -0
- package/src/builtin-addons/weather/poller.ts +70 -0
- package/src/builtin-addons/weather/provider/city-key.ts +9 -0
- package/src/builtin-addons/weather/provider/codes.ts +35 -0
- package/src/builtin-addons/weather/provider/fetch.ts +97 -0
- package/src/builtin-addons/weather/provider/geocode.ts +67 -0
- package/src/builtin-addons/weather/provider/types.ts +42 -0
- package/src/builtin-addons/weather/sirenodeck.json +6 -0
- package/src/cli/coding-agents-onboarding.ts +434 -0
- package/src/cli/commands/addon-decks.ts +441 -0
- package/src/cli/commands/addon-registry.ts +639 -0
- package/src/cli/commands/emulator-mode.ts +289 -0
- package/src/cli/commands/install.ts +128 -0
- package/src/cli/commands/logs.ts +74 -0
- package/src/cli/commands/network-bind.ts +111 -0
- package/src/cli/commands/pipeline/helpers.ts +75 -0
- package/src/cli/commands/pipeline/preflight.ts +20 -0
- package/src/cli/commands/port-identity.ts +152 -0
- package/src/cli/commands/reload.ts +37 -0
- package/src/cli/commands/restart.ts +139 -0
- package/src/cli/commands/run.ts +2270 -0
- package/src/cli/commands/service/install.ts +121 -0
- package/src/cli/commands/service/render-template.ts +158 -0
- package/src/cli/commands/service-manager.ts +184 -0
- package/src/cli/commands/spawn-daemon.ts +251 -0
- package/src/cli/commands/start.ts +1061 -0
- package/src/cli/commands/status.ts +110 -0
- package/src/cli/commands/stop.ts +121 -0
- package/src/cli/commands/subprocess-supervisor.ts +203 -0
- package/src/cli/commands/system-requirements.ts +699 -0
- package/src/cli/cwd.ts +28 -0
- package/src/cli/first-run.ts +149 -0
- package/src/cli/http-server.ts +244 -0
- package/src/cli/index.ts +355 -0
- package/src/cli/install-root.ts +26 -0
- package/src/cli/main.ts +194 -0
- package/src/cli/package-manager.ts +175 -0
- package/src/cli/probe-deps.ts +45 -0
- package/src/cli/prompt.ts +22 -0
- package/src/cli/startup-display.ts +452 -0
- package/src/config/builtin-icons.ts +57 -0
- package/src/config/config-diff.ts +54 -0
- package/src/config/discovery.ts +64 -0
- package/src/config/icon-resolver.ts +90 -0
- package/src/config/include-resolver.ts +202 -0
- package/src/config/index.ts +9 -0
- package/src/config/loader.ts +187 -0
- package/src/config/mutation.ts +514 -0
- package/src/config/reference-expander.ts +84 -0
- package/src/config/schemas.ts +223 -0
- package/src/config/validation.ts +254 -0
- package/src/core/asset-id.ts +21 -0
- package/src/core/gesture-state.ts +344 -0
- package/src/core/icon-asset-registry.ts +127 -0
- package/src/core/icon-source.ts +86 -0
- package/src/core/index.ts +30 -0
- package/src/core/mime.ts +16 -0
- package/src/core/pagination.ts +90 -0
- package/src/core/pub-sub.ts +126 -0
- package/src/core/settings.ts +153 -0
- package/src/core/store.ts +88 -0
- package/src/core/watcher.ts +73 -0
- package/src/deck/addon-handler-bridge.ts +516 -0
- package/src/deck/deck-config.ts +271 -0
- package/src/deck/editor-surfaces.ts +87 -0
- package/src/deck/host-context.ts +18 -0
- package/src/deck/index.ts +102 -0
- package/src/deck/macro-parse.ts +182 -0
- package/src/deck/methods.ts +348 -0
- package/src/deck/paginate-deck.ts +98 -0
- package/src/deck/position-buttons.ts +57 -0
- package/src/deck/runtime/index.ts +5 -0
- package/src/deck/runtime/runtime.ts +1060 -0
- package/src/deck/runtime-subscriptions.ts +45 -0
- package/src/deck/system-back-injection.ts +62 -0
- package/src/deck/system-buttons/registry.tsx +97 -0
- package/src/deck/system-buttons/types.ts +14 -0
- package/src/device/black-frame.ts +73 -0
- package/src/device/index.ts +13 -0
- package/src/device/linux-udev.ts +36 -0
- package/src/device/models.ts +89 -0
- package/src/device/reconnecting.ts +193 -0
- package/src/device/registry.ts +38 -0
- package/src/device/stream-deck.ts +155 -0
- package/src/index.ts +74 -0
- package/src/outputClient/emulator.ts +357 -0
- package/src/outputClient/index.ts +26 -0
- package/src/outputClient/open-browser.ts +26 -0
- package/src/outputClient/real.ts +393 -0
- package/src/outputClient/types.ts +83 -0
- package/src/render/browser-renderer.ts +240 -0
- package/src/render/buffer-hash.ts +20 -0
- package/src/render/editor-handler.ts +197 -0
- package/src/render/emulator-server.ts +128 -0
- package/src/render/icon-source-resolver.ts +69 -0
- package/src/render/index.ts +16 -0
- package/src/render/protocol.ts +61 -0
- package/src/render/push-raw-image.ts +100 -0
- package/src/render/screenshot-cadence.ts +87 -0
- package/src/render/state-publisher.ts +110 -0
- package/src/render/vite-server.ts +138 -0
- package/src/render/ws-bridge.ts +315 -0
- package/src/system/device-selection.ts +80 -0
- package/src/system/glob-match.ts +74 -0
- package/src/system/providers/active-app/darwin.ts +117 -0
- package/src/system/providers/active-app/linux.ts +245 -0
- package/src/system/providers/active-app/wayland-gnome.ts +303 -0
- package/src/system/providers/active-app/windows.ts +94 -0
- package/src/system/providers/active-app.ts +97 -0
- package/src/system/providers/clipboard/darwin.ts +65 -0
- package/src/system/providers/clipboard/linux.ts +163 -0
- package/src/system/providers/clipboard/windows.ts +94 -0
- package/src/system/providers/clipboard.ts +84 -0
- package/src/system/providers/error.ts +16 -0
- package/src/system/providers/key-macro/darwin.ts +221 -0
- package/src/system/providers/key-macro/linux.ts +651 -0
- package/src/system/providers/key-macro/parser.ts +215 -0
- package/src/system/providers/key-macro/windows.ts +476 -0
- package/src/system/providers/key-macro.ts +79 -0
- package/src/system/providers/notification/darwin.ts +36 -0
- package/src/system/providers/notification/linux.ts +115 -0
- package/src/system/providers/notification/windows.ts +72 -0
- package/src/system/providers/notification.ts +75 -0
- package/src/system/providers/session/darwin.ts +82 -0
- package/src/system/providers/session/linux.ts +175 -0
- package/src/system/providers/session/windows.ts +73 -0
- package/src/system/providers/session.ts +89 -0
- package/src/system/providers/shared.ts +65 -0
- package/src/system/requirements.ts +235 -0
- package/src/system/setup-wizard/config-seed.ts +30 -0
- package/src/system/setup-wizard/format.ts +275 -0
- package/src/system/setup-wizard/index.ts +45 -0
- package/src/system/setup-wizard/open-settings.ts +26 -0
- package/src/system/setup-wizard/plan.ts +230 -0
- package/src/system/setup-wizard/probe-cache.ts +34 -0
- package/src/system/setup-wizard/probe.ts +486 -0
- package/src/system/setup-wizard/runtime-features.ts +85 -0
- package/src/system/setup-wizard/sudo.ts +178 -0
- package/src/system/setup-wizard/types.ts +133 -0
- package/src/system/virtual-stream-deck.ts +67 -0
- package/src/themes/css.ts +210 -0
- package/src/themes/default/assets/IBMPlexSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- package/src/themes/default/assets/IBMPlexSans-VariableFont_wdth,wght.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Bold.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Italic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Light.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Medium.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Regular.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Thin.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/OFL.txt +93 -0
- package/src/themes/default/assets/OFL.txt +93 -0
- package/src/themes/default/assets/README.txt +106 -0
- package/src/themes/default/assets/static/IBMPlexSans-Bold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-BoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-ExtraLight.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-ExtraLightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-Italic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-Light.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-LightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-Medium.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-MediumItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-Regular.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-SemiBold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-SemiBoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-Thin.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-ThinItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Bold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-BoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-ExtraLight.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-ExtraLightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Italic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Light.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-LightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Medium.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-MediumItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Regular.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-SemiBold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-SemiBoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Thin.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-ThinItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Bold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-BoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ExtraLight.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ExtraLightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Italic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Light.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-LightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Medium.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-MediumItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Regular.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-SemiBold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Thin.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ThinItalic.ttf +0 -0
- package/src/themes/default/components.css +81 -0
- package/src/themes/default/sirenodeck.json +170 -0
- package/src/themes/index.ts +14 -0
- package/src/themes/light/assets/IBMPlexSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- package/src/themes/light/assets/IBMPlexSans-VariableFont_wdth,wght.ttf +0 -0
- package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Bold.ttf +0 -0
- package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Medium.ttf +0 -0
- package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Regular.ttf +0 -0
- package/src/themes/light/assets/IBM_Plex_Mono/OFL.txt +93 -0
- package/src/themes/light/assets/OFL.txt +93 -0
- package/src/themes/light/assets/README.txt +106 -0
- package/src/themes/light/assets/static/IBMPlexSans-Light.ttf +0 -0
- package/src/themes/light/assets/static/IBMPlexSans-LightItalic.ttf +0 -0
- package/src/themes/light/assets/static/IBMPlexSans-Thin.ttf +0 -0
- package/src/themes/light/components.css +70 -0
- package/src/themes/light/sirenodeck.json +170 -0
- package/src/themes/loader.ts +343 -0
- package/src/themes/manifest.ts +164 -0
- package/src/themes/use-resolved-theme.tsx +32 -0
- package/src/ui/ButtonFrame.tsx +230 -0
- package/src/ui/contexts/AssetCacheContext.tsx +43 -0
- package/src/ui/index.ts +20 -0
- package/src/ui/primitives/Chip.tsx +53 -0
- package/src/ui/primitives/Icon.tsx +209 -0
- package/src/ui/primitives/Label.tsx +82 -0
- package/src/ui/primitives/ProgressBar.tsx +38 -0
- package/src/ui/primitives/TapIndicator.tsx +67 -0
- package/src/ui/primitives/Text.tsx +810 -0
- package/src/ui/primitives/index.ts +6 -0
- package/src/ui/surfaces/BarsSurface.tsx +211 -0
- package/src/ui/surfaces/IconLabelProgressSurface.tsx +133 -0
- package/src/ui/surfaces/IconLabelSurface.tsx +59 -0
- package/src/ui/surfaces/LabelValueListSurface.tsx +202 -0
- package/src/ui/surfaces/PaginatedSurface.tsx +164 -0
- package/src/ui/surfaces/SplitActionSurface.tsx +98 -0
- package/src/ui/surfaces/TemporaryErrorSurface.tsx +68 -0
- package/src/ui/surfaces/ValueChart.tsx +218 -0
- package/src/ui/surfaces/index.ts +32 -0
- package/src/ui/surfaces/paginated-schema.ts +22 -0
- package/src/ui/theme-presentation-builder.ts +184 -0
- package/src/ui/theme-presentation.tsx +146 -0
- package/src/ui/utils/cn.ts +12 -0
- package/src/util/cache-paths.ts +38 -0
- package/src/util/config-diff.ts +16 -0
- package/src/util/daemon-control.ts +85 -0
- package/src/util/daemon.ts +575 -0
- package/src/util/device-config.ts +56 -0
- package/src/util/errors.ts +13 -0
- package/src/util/log-reader.ts +114 -0
- package/src/util/log-tail.ts +103 -0
- package/src/util/logger.ts +417 -0
- package/src/util/process-title.ts +79 -0
- package/src/util/single-instance.ts +202 -0
- package/src/version.ts +10 -0
- package/src/vite/index.ts +6 -0
- package/src/vite/virtual-modules.ts +631 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
import type pino from "pino"
|
|
2
|
+
|
|
3
|
+
import type { PubSub } from "@/core/pub-sub"
|
|
4
|
+
import type { Store } from "@/core/store"
|
|
5
|
+
import type { GestureKind } from "@/core/gesture-state"
|
|
6
|
+
import type { ActiveAppProvider } from "@/system/providers/active-app"
|
|
7
|
+
import type { SessionProvider, SessionState } from "@/system/providers/session"
|
|
8
|
+
import { getRequiredCapability } from "@/system/requirements"
|
|
9
|
+
import { compileDeckMatcher } from "@/system/glob-match"
|
|
10
|
+
import type { Methods } from "../methods"
|
|
11
|
+
import { describeEditorSurfaces } from "../editor-surfaces"
|
|
12
|
+
import type {
|
|
13
|
+
EditorAddonOwner,
|
|
14
|
+
EditorSourceTarget,
|
|
15
|
+
EditorSurfaceDescriptor,
|
|
16
|
+
} from "../editor-surfaces"
|
|
17
|
+
|
|
18
|
+
type ActiveAppProviderLike = Pick<ActiveAppProvider, "getActive" | "stop">
|
|
19
|
+
|
|
20
|
+
export interface RuntimeButton {
|
|
21
|
+
id: string
|
|
22
|
+
type: string
|
|
23
|
+
position?: number
|
|
24
|
+
config?: unknown
|
|
25
|
+
actions?: {
|
|
26
|
+
tap?: string
|
|
27
|
+
dbltap?: string
|
|
28
|
+
hold?: string
|
|
29
|
+
}
|
|
30
|
+
full?: boolean
|
|
31
|
+
variant?: string
|
|
32
|
+
buttonColor?:
|
|
33
|
+
| "blue"
|
|
34
|
+
| "green"
|
|
35
|
+
| "purple"
|
|
36
|
+
| "cyan"
|
|
37
|
+
| "magenta"
|
|
38
|
+
| "amber"
|
|
39
|
+
| "lime"
|
|
40
|
+
sourceTarget?: EditorSourceTarget
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface RuntimeDeck {
|
|
44
|
+
id: string
|
|
45
|
+
name: string
|
|
46
|
+
buttons: ReadonlyArray<RuntimeButton>
|
|
47
|
+
isMain?: boolean
|
|
48
|
+
processNames?: ReadonlyArray<string>
|
|
49
|
+
windowNames?: ReadonlyArray<string>
|
|
50
|
+
autoShow?: boolean
|
|
51
|
+
isOverlayDeck?: boolean
|
|
52
|
+
icon?: string
|
|
53
|
+
background?: string
|
|
54
|
+
paginated?: boolean
|
|
55
|
+
// ponytail: addon-generated decks inherit the parent's window/process trigger
|
|
56
|
+
// so the runtime can switch to them automatically when the watched window
|
|
57
|
+
// comes to the foreground. Preserved through materialization.
|
|
58
|
+
trigger?: unknown
|
|
59
|
+
isOverlay?: boolean
|
|
60
|
+
config?: Record<string, unknown>
|
|
61
|
+
buttonColor?:
|
|
62
|
+
| "blue"
|
|
63
|
+
| "green"
|
|
64
|
+
| "purple"
|
|
65
|
+
| "cyan"
|
|
66
|
+
| "magenta"
|
|
67
|
+
| "amber"
|
|
68
|
+
| "lime"
|
|
69
|
+
/**
|
|
70
|
+
* Variant token name resolved from the active theme. Falls back to
|
|
71
|
+
* "default" when the theme doesn't declare the requested variant.
|
|
72
|
+
*/
|
|
73
|
+
variant?: string
|
|
74
|
+
buttonErrors?: ReadonlyArray<{
|
|
75
|
+
position: number
|
|
76
|
+
buttonId?: string
|
|
77
|
+
details: string
|
|
78
|
+
}>
|
|
79
|
+
sourceDeckId?: string
|
|
80
|
+
projectionId?: string
|
|
81
|
+
pageIndex?: number
|
|
82
|
+
editable?: boolean
|
|
83
|
+
addonOwner?: EditorAddonOwner
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface RuntimeButtonHandler {
|
|
87
|
+
onTap?: (ctx: ButtonActionContext) => void | Promise<void>
|
|
88
|
+
onDblTap?: (ctx: ButtonActionContext) => void | Promise<void>
|
|
89
|
+
onHold?: (ctx: ButtonActionContext) => void | Promise<void>
|
|
90
|
+
dispose?: () => void | Promise<void>
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ButtonActionContext {
|
|
94
|
+
buttonId: string
|
|
95
|
+
deckId: string
|
|
96
|
+
config: unknown
|
|
97
|
+
gesture: GestureKind
|
|
98
|
+
position: number | undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface GestureEvent {
|
|
102
|
+
readonly gesture: GestureKind
|
|
103
|
+
readonly at: number
|
|
104
|
+
readonly position: number | undefined
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type GestureListener = (buttonId: string, event: GestureEvent) => void
|
|
108
|
+
|
|
109
|
+
export interface MountedButton {
|
|
110
|
+
addonName: string
|
|
111
|
+
buttonId: string
|
|
112
|
+
type: string
|
|
113
|
+
config: unknown
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface CreateRuntimeOptions {
|
|
117
|
+
decks: ReadonlyArray<RuntimeDeck>
|
|
118
|
+
pubSub: PubSub
|
|
119
|
+
store: Store
|
|
120
|
+
logger: pino.Logger
|
|
121
|
+
getMethods: () => Methods
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface Runtime {
|
|
125
|
+
getActiveDeck(): RuntimeDeck
|
|
126
|
+
getActiveDeckId(): string
|
|
127
|
+
deckExists(id: string): boolean
|
|
128
|
+
setDecks(decks: ReadonlyArray<RuntimeDeck>): void
|
|
129
|
+
navigateToDeck(id: string, options?: { addToHistory?: boolean }): void
|
|
130
|
+
goBack(): void
|
|
131
|
+
setOverlay(
|
|
132
|
+
deckId: string | null,
|
|
133
|
+
opts?: { source?: "autoShow" | "manual" },
|
|
134
|
+
): void
|
|
135
|
+
getOverlay(): RuntimeDeck | null
|
|
136
|
+
registerButtonHandler(buttonId: string, handler: RuntimeButtonHandler): void
|
|
137
|
+
unregisterButtonHandler(buttonId: string): void
|
|
138
|
+
mountAddonButtons(
|
|
139
|
+
addonName: string,
|
|
140
|
+
buttons: ReadonlyArray<MountedButton>,
|
|
141
|
+
): void
|
|
142
|
+
dispatchGesture(buttonId: string, gesture: GestureKind): Promise<void>
|
|
143
|
+
invokeAction(buttonId: string, gesture: GestureKind): Promise<void>
|
|
144
|
+
setGestureListener(listener: GestureListener | null): void
|
|
145
|
+
invalidate(): void
|
|
146
|
+
setActiveAppProvider(provider: ActiveAppProviderLike): void
|
|
147
|
+
setSessionProvider(provider: SessionProvider): void
|
|
148
|
+
isLockActive(): boolean
|
|
149
|
+
stopActiveAppPolling(): Promise<void>
|
|
150
|
+
navStackDepth(): number
|
|
151
|
+
hasOverlayDeckAvailable(): boolean
|
|
152
|
+
getAvailableOverlayDeckIcon(): string | null
|
|
153
|
+
getAvailableOverlayDeckName(): string | null
|
|
154
|
+
getBrightness(): number
|
|
155
|
+
setBrightness(value: number): void
|
|
156
|
+
getEditorSurfaces(options?: {
|
|
157
|
+
keyCount?: number
|
|
158
|
+
revision?: number
|
|
159
|
+
}): EditorSurfaceDescriptor[]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const createRuntime = (options: CreateRuntimeOptions): Runtime => {
|
|
163
|
+
const { pubSub, store, getMethods } = options
|
|
164
|
+
const logger = options.logger.child({ component: "runtime" })
|
|
165
|
+
let { decks } = options
|
|
166
|
+
let gestureListener: GestureListener | null = null
|
|
167
|
+
const mainDeck = decks.find((d) => d.isMain) ?? decks[0]
|
|
168
|
+
if (mainDeck === undefined) {
|
|
169
|
+
throw new Error("createRuntime: at least one deck is required")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const handlers = new Map<string, RuntimeButtonHandler>()
|
|
173
|
+
const navStack: string[] = [mainDeck.id]
|
|
174
|
+
let transientDeckId: string | null = null
|
|
175
|
+
let overlayDeckId: string | null = null
|
|
176
|
+
let availableOverlayDeckId: string | null = null
|
|
177
|
+
let overlayPreviousActiveId: string | null = null
|
|
178
|
+
const overlayNavStacks = new Map<string, string[]>()
|
|
179
|
+
const buttonIndex = new Map<
|
|
180
|
+
string,
|
|
181
|
+
{ deckId: string; button: RuntimeDeck["buttons"][number] }
|
|
182
|
+
>()
|
|
183
|
+
for (const deck of decks) {
|
|
184
|
+
for (const button of deck.buttons) {
|
|
185
|
+
if (!buttonIndex.has(button.id)) {
|
|
186
|
+
buttonIndex.set(button.id, { deckId: deck.id, button })
|
|
187
|
+
}
|
|
188
|
+
// ponytail: also index the composite "{deckId}:{button.id}" so callers
|
|
189
|
+
// can dispatch by either form. findButton consults both maps.
|
|
190
|
+
const composite = `${deck.id}:${button.id}`
|
|
191
|
+
if (!buttonIndex.has(composite)) {
|
|
192
|
+
buttonIndex.set(composite, { deckId: deck.id, button })
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
let brightness = 50
|
|
197
|
+
let lockActive = false
|
|
198
|
+
let preLockActiveDeckId: string | null = null
|
|
199
|
+
let preLockOverlayDeckId: string | null = null
|
|
200
|
+
let sessionUnsubscribe: (() => void) | null = null
|
|
201
|
+
|
|
202
|
+
const deckById = (id: string): RuntimeDeck | undefined =>
|
|
203
|
+
decks.find((d) => d.id === id)
|
|
204
|
+
|
|
205
|
+
const setDecks = (next: ReadonlyArray<RuntimeDeck>): void => {
|
|
206
|
+
decks = next
|
|
207
|
+
// ponytail: rebuild a global button-id → ref map so bare-id lookups in
|
|
208
|
+
// findButton are O(1) instead of scanning all decks in deck order. Set
|
|
209
|
+
// first-wins (not last-wins) to preserve the legacy scan behavior.
|
|
210
|
+
buttonIndex.clear()
|
|
211
|
+
for (const deck of decks) {
|
|
212
|
+
if (!Array.isArray(deck.buttons)) continue
|
|
213
|
+
for (const button of deck.buttons) {
|
|
214
|
+
if (!buttonIndex.has(button.id)) {
|
|
215
|
+
buttonIndex.set(button.id, { deckId: deck.id, button })
|
|
216
|
+
}
|
|
217
|
+
const composite = `${deck.id}:${button.id}`
|
|
218
|
+
if (!buttonIndex.has(composite)) {
|
|
219
|
+
buttonIndex.set(composite, { deckId: deck.id, button })
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Clear any navigation/overlay state that pointed at decks we no longer have.
|
|
224
|
+
const currentActive = transientDeckId ?? navStack[navStack.length - 1]
|
|
225
|
+
if (currentActive !== undefined && deckById(currentActive) === undefined) {
|
|
226
|
+
navStack.length = 0
|
|
227
|
+
const fallback = decks.find((d) => d.isMain) ?? decks[0]
|
|
228
|
+
if (fallback !== undefined) navStack.push(fallback.id)
|
|
229
|
+
transientDeckId = null
|
|
230
|
+
}
|
|
231
|
+
if (overlayDeckId !== null && deckById(overlayDeckId) === undefined) {
|
|
232
|
+
// ponytail: clear both bits so a later setOverlay(null) doesn't restore
|
|
233
|
+
// a deck id that no longer exists in the registry.
|
|
234
|
+
overlayDeckId = null
|
|
235
|
+
overlayPreviousActiveId = null
|
|
236
|
+
}
|
|
237
|
+
pubSub.publish("runtime:activeDeck", { deckId: getActiveDeckId() })
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const findButton = (
|
|
241
|
+
id: string,
|
|
242
|
+
): { deckId: string; button: RuntimeDeck["buttons"][number] } | null => {
|
|
243
|
+
// ponytail: build a global button-id index in setDecks so bare-id
|
|
244
|
+
// lookups don't depend on deck order.
|
|
245
|
+
return buttonIndex.get(id) ?? null
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ponytail: lock-mode escape hatch — only these button types can navigate out of lock mode
|
|
249
|
+
const LOCK_FOLDER_NAV_TYPES: ReadonlySet<string> = new Set([
|
|
250
|
+
"core:change-deck",
|
|
251
|
+
"core:page-nav",
|
|
252
|
+
])
|
|
253
|
+
|
|
254
|
+
const getActiveDeck = (): RuntimeDeck => {
|
|
255
|
+
const id = getActiveDeckId()
|
|
256
|
+
const deck = deckById(id)
|
|
257
|
+
if (deck === undefined) throw new Error(`Active deck '${id}' not found`)
|
|
258
|
+
return deck
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const getActiveDeckId = (): string => {
|
|
262
|
+
if (lockActive) return "core:lock"
|
|
263
|
+
// ponytail: while overlay mode is on, transientDeckId (used by page-nav)
|
|
264
|
+
// must take precedence — otherwise paginated overlay pages are invisible
|
|
265
|
+
// to the runtime, and dismissing overlay after paginating restores the
|
|
266
|
+
// overlay root instead of the page the user was on.
|
|
267
|
+
if (overlayDeckId !== null) {
|
|
268
|
+
if (transientDeckId !== null) return transientDeckId
|
|
269
|
+
const stack = overlayNavStacks.get(overlayDeckId)
|
|
270
|
+
if (stack !== undefined && stack.length > 0) {
|
|
271
|
+
return stack[stack.length - 1] ?? overlayDeckId
|
|
272
|
+
}
|
|
273
|
+
return overlayDeckId
|
|
274
|
+
}
|
|
275
|
+
return transientDeckId ?? navStack[navStack.length - 1] ?? mainDeck.id
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const snapshotRegularActiveDeckId = (): string =>
|
|
279
|
+
overlayPreviousActiveId ??
|
|
280
|
+
transientDeckId ??
|
|
281
|
+
navStack[navStack.length - 1] ??
|
|
282
|
+
mainDeck.id
|
|
283
|
+
|
|
284
|
+
const enterLockMode = (): void => {
|
|
285
|
+
if (lockActive) return
|
|
286
|
+
preLockActiveDeckId = snapshotRegularActiveDeckId()
|
|
287
|
+
preLockOverlayDeckId = overlayDeckId
|
|
288
|
+
lockActive = true
|
|
289
|
+
logger.info(
|
|
290
|
+
{ preLockActiveDeckId, preLockOverlayDeckId },
|
|
291
|
+
"runtime: lock active",
|
|
292
|
+
)
|
|
293
|
+
pubSub.publish("runtime:lock-mode", {
|
|
294
|
+
active: true,
|
|
295
|
+
reason: "session-locked",
|
|
296
|
+
})
|
|
297
|
+
pubSub.publish("runtime:activeDeck", { deckId: "core:lock" })
|
|
298
|
+
pubSub.publish("runtime:invalidate", undefined)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const navigateToDeck = (
|
|
302
|
+
id: string,
|
|
303
|
+
navOptions?: { addToHistory?: boolean },
|
|
304
|
+
): void => {
|
|
305
|
+
if (id === getActiveDeckId()) return
|
|
306
|
+
let resolvedId = id
|
|
307
|
+
let target = deckById(id)
|
|
308
|
+
// ponytail: paginated decks drop the base id (e.g. `demo-media` becomes
|
|
309
|
+
// `demo-media-p1`/`demo-media-p2`). Fall back to the first paginated page
|
|
310
|
+
// when the base id no longer exists, so `core:change-deck` targets keep
|
|
311
|
+
// working without callers knowing about pagination.
|
|
312
|
+
if (target === undefined) {
|
|
313
|
+
const firstPage = deckById(`${id}-p1`)
|
|
314
|
+
if (firstPage !== undefined) {
|
|
315
|
+
target = firstPage
|
|
316
|
+
resolvedId = firstPage.id
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (target === undefined) {
|
|
320
|
+
logger.warn({ deckId: id }, "navigateToDeck: deck not found")
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
const previousActiveId = getActiveDeckId()
|
|
324
|
+
if (overlayDeckId !== null) {
|
|
325
|
+
// ponytail: while overlay mode is on, both change-deck (addToHistory:true)
|
|
326
|
+
// and page-nav (addToHistory:false) push onto the overlay's nav stack so
|
|
327
|
+
// re-toggling the overlay restores the user's last position. Page-nav
|
|
328
|
+
// used to set transientDeckId which the runtime dropped on every
|
|
329
|
+
// getActiveDeckId query — see https://example.invalid/quick-009.
|
|
330
|
+
const stack = overlayNavStacks.get(overlayDeckId)
|
|
331
|
+
if (stack === undefined) {
|
|
332
|
+
overlayNavStacks.set(overlayDeckId, [overlayDeckId])
|
|
333
|
+
}
|
|
334
|
+
const current = overlayNavStacks.get(overlayDeckId)!
|
|
335
|
+
if (current[current.length - 1] !== resolvedId) {
|
|
336
|
+
current.push(resolvedId)
|
|
337
|
+
}
|
|
338
|
+
transientDeckId = null
|
|
339
|
+
} else if (navOptions?.addToHistory === false) {
|
|
340
|
+
transientDeckId = resolvedId
|
|
341
|
+
} else {
|
|
342
|
+
navStack.push(resolvedId)
|
|
343
|
+
transientDeckId = null
|
|
344
|
+
}
|
|
345
|
+
pubSub.publish("runtime:deck-inactive", { deckId: previousActiveId })
|
|
346
|
+
pubSub.publish("runtime:activeDeck", { deckId: resolvedId })
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const goBack = (): void => {
|
|
350
|
+
if (overlayDeckId !== null) {
|
|
351
|
+
const stack = overlayNavStacks.get(overlayDeckId)
|
|
352
|
+
if (stack !== undefined && stack.length > 1) {
|
|
353
|
+
stack.pop()
|
|
354
|
+
const prev = stack[stack.length - 1]!
|
|
355
|
+
pubSub.publish("runtime:deck-inactive", { deckId: prev })
|
|
356
|
+
pubSub.publish("runtime:activeDeck", { deckId: prev })
|
|
357
|
+
} else {
|
|
358
|
+
setOverlay(null)
|
|
359
|
+
}
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
if (transientDeckId !== null) {
|
|
363
|
+
pubSub.publish("runtime:deck-inactive", { deckId: transientDeckId })
|
|
364
|
+
const pageMatch = /^(.*)-p\d+$/.exec(transientDeckId)
|
|
365
|
+
if (pageMatch !== null) {
|
|
366
|
+
const basePageId = `${pageMatch[1]}-p1`
|
|
367
|
+
if (navStack[navStack.length - 1] === basePageId) {
|
|
368
|
+
navStack.pop()
|
|
369
|
+
pubSub.publish("runtime:deck-inactive", { deckId: basePageId })
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
transientDeckId = null
|
|
373
|
+
const prev = navStack[navStack.length - 1] ?? mainDeck.id
|
|
374
|
+
pubSub.publish("runtime:activeDeck", { deckId: prev })
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
if (navStack.length <= 1) {
|
|
378
|
+
logger.warn("goBack: at root deck; nothing to pop")
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
const popped = navStack[navStack.length - 1]
|
|
382
|
+
navStack.pop()
|
|
383
|
+
const prev = navStack[navStack.length - 1]
|
|
384
|
+
if (prev === undefined) return
|
|
385
|
+
if (popped !== undefined) {
|
|
386
|
+
pubSub.publish("runtime:deck-inactive", { deckId: popped })
|
|
387
|
+
}
|
|
388
|
+
pubSub.publish("runtime:activeDeck", { deckId: prev })
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const setOverlay = (
|
|
392
|
+
deckId: string | null,
|
|
393
|
+
opts?: { source?: "autoShow" | "manual" },
|
|
394
|
+
): void => {
|
|
395
|
+
if (deckId !== null && deckById(deckId) === undefined) {
|
|
396
|
+
logger.warn({ deckId }, "setOverlay: deck not found")
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
if (deckId !== null && !overlayNavStacks.has(deckId)) {
|
|
400
|
+
overlayNavStacks.set(deckId, [deckId])
|
|
401
|
+
}
|
|
402
|
+
const previousActiveId = getActiveDeckId()
|
|
403
|
+
const previousOverlayId = overlayDeckId
|
|
404
|
+
if (previousOverlayId === null && deckId !== null) {
|
|
405
|
+
overlayPreviousActiveId = previousActiveId
|
|
406
|
+
}
|
|
407
|
+
overlayDeckId = deckId
|
|
408
|
+
pubSub.publish(
|
|
409
|
+
"runtime:overlay",
|
|
410
|
+
opts?.source !== undefined ? { deckId, source: opts.source } : { deckId },
|
|
411
|
+
)
|
|
412
|
+
if (deckId !== null) {
|
|
413
|
+
if (previousOverlayId !== null) {
|
|
414
|
+
pubSub.publish("runtime:deck-inactive", { deckId: previousOverlayId })
|
|
415
|
+
} else {
|
|
416
|
+
pubSub.publish("runtime:deck-inactive", { deckId: previousActiveId })
|
|
417
|
+
}
|
|
418
|
+
// ponytail: if the overlay has nav history (stack top != deckId), the
|
|
419
|
+
// user was already navigating within this overlay. Publish the stack
|
|
420
|
+
// top so the broadcast matches what getActiveDeckId will report, and
|
|
421
|
+
// re-toggling restores the page the user was on.
|
|
422
|
+
const stack = overlayNavStacks.get(deckId)
|
|
423
|
+
const targetDeck =
|
|
424
|
+
stack !== undefined && stack.length > 0
|
|
425
|
+
? (stack[stack.length - 1] ?? deckId)
|
|
426
|
+
: deckId
|
|
427
|
+
pubSub.publish("runtime:activeDeck", { deckId: targetDeck })
|
|
428
|
+
} else {
|
|
429
|
+
// ponytail: previousActiveId (e.g. a paginated page like -p2) is the
|
|
430
|
+
// deck actually being deactivated; previousOverlayId is the overlay
|
|
431
|
+
// root. Publish the page so subscribers see the right deck going dark.
|
|
432
|
+
if (previousActiveId !== previousOverlayId) {
|
|
433
|
+
pubSub.publish("runtime:deck-inactive", { deckId: previousActiveId })
|
|
434
|
+
}
|
|
435
|
+
// ponytail: clear transientDeckId so the dismissed overlay's paginated
|
|
436
|
+
// page doesn't bleed into the regular nav state. Without this,
|
|
437
|
+
// getActiveDeckId keeps returning the page even after overlayDeckId
|
|
438
|
+
// is null.
|
|
439
|
+
transientDeckId = null
|
|
440
|
+
const restoreId = overlayPreviousActiveId ?? mainDeck.id
|
|
441
|
+
overlayPreviousActiveId = null
|
|
442
|
+
pubSub.publish("runtime:activeDeck", { deckId: restoreId })
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const getOverlay = (): RuntimeDeck | null => {
|
|
447
|
+
if (overlayDeckId === null) return null
|
|
448
|
+
return deckById(overlayDeckId) ?? null
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const registerButtonHandler = (
|
|
452
|
+
buttonId: string,
|
|
453
|
+
handler: RuntimeButtonHandler,
|
|
454
|
+
): void => {
|
|
455
|
+
handlers.set(buttonId, handler)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const unregisterButtonHandler = (buttonId: string): void => {
|
|
459
|
+
handlers.delete(buttonId)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const mountAddonButtons = (
|
|
463
|
+
addonName: string,
|
|
464
|
+
buttons: ReadonlyArray<MountedButton>,
|
|
465
|
+
): void => {
|
|
466
|
+
for (const btn of buttons) {
|
|
467
|
+
logger.debug(
|
|
468
|
+
{ addonName, buttonId: btn.buttonId, type: btn.type },
|
|
469
|
+
"mounted addon button",
|
|
470
|
+
)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const handleSystemButton = (type: string, gesture: GestureKind): boolean => {
|
|
475
|
+
if (type === "core:back") {
|
|
476
|
+
if (gesture === "tap") {
|
|
477
|
+
goBack()
|
|
478
|
+
return true
|
|
479
|
+
}
|
|
480
|
+
if (gesture === "hold") {
|
|
481
|
+
// ponytail: hold always goes to main, never toggles overlay
|
|
482
|
+
if (overlayDeckId !== null) setOverlay(null)
|
|
483
|
+
navStack.length = 0
|
|
484
|
+
navStack.push(mainDeck.id)
|
|
485
|
+
const prevTransient = transientDeckId
|
|
486
|
+
transientDeckId = null
|
|
487
|
+
if (prevTransient !== null) {
|
|
488
|
+
pubSub.publish("runtime:deck-inactive", { deckId: prevTransient })
|
|
489
|
+
}
|
|
490
|
+
pubSub.publish("runtime:activeDeck", { deckId: mainDeck.id })
|
|
491
|
+
return true
|
|
492
|
+
}
|
|
493
|
+
if (gesture === "dbl-tap") {
|
|
494
|
+
if (overlayDeckId !== null) {
|
|
495
|
+
setOverlay(null)
|
|
496
|
+
} else if (availableOverlayDeckId !== null) {
|
|
497
|
+
setOverlay(availableOverlayDeckId)
|
|
498
|
+
}
|
|
499
|
+
return true
|
|
500
|
+
}
|
|
501
|
+
return true
|
|
502
|
+
}
|
|
503
|
+
// ponytail: gesture table — see ARCHITECTURE §5.
|
|
504
|
+
// core:settings-entry tap = open settings overlay; dbl-tap = toggle overlay.
|
|
505
|
+
// core:overlay-toggle tap/dbl-tap = toggle overlay (legacy fallback).
|
|
506
|
+
if (type === "core:settings-entry") {
|
|
507
|
+
if (gesture === "tap") {
|
|
508
|
+
navigateToDeck("internal-settings:settings", { addToHistory: true })
|
|
509
|
+
return true
|
|
510
|
+
}
|
|
511
|
+
if (gesture === "dbl-tap") {
|
|
512
|
+
if (overlayDeckId !== null) {
|
|
513
|
+
setOverlay(null)
|
|
514
|
+
} else if (availableOverlayDeckId !== null) {
|
|
515
|
+
setOverlay(availableOverlayDeckId)
|
|
516
|
+
}
|
|
517
|
+
return true
|
|
518
|
+
}
|
|
519
|
+
return false
|
|
520
|
+
}
|
|
521
|
+
if (type === "core:overlay-toggle") {
|
|
522
|
+
// ponytail: tap = step back within overlay path; dbl-tap = toggle
|
|
523
|
+
// overlay on/off; hold is left unhandled so it can fall through if the
|
|
524
|
+
// user assigns a hold action elsewhere.
|
|
525
|
+
if (gesture === "tap") {
|
|
526
|
+
goBack()
|
|
527
|
+
return true
|
|
528
|
+
}
|
|
529
|
+
if (gesture === "dbl-tap") {
|
|
530
|
+
if (overlayDeckId !== null) {
|
|
531
|
+
setOverlay(null)
|
|
532
|
+
} else if (availableOverlayDeckId !== null) {
|
|
533
|
+
setOverlay(availableOverlayDeckId)
|
|
534
|
+
}
|
|
535
|
+
return true
|
|
536
|
+
}
|
|
537
|
+
return false
|
|
538
|
+
}
|
|
539
|
+
return false
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const invokeAction = async (
|
|
543
|
+
buttonId: string,
|
|
544
|
+
gesture: GestureKind,
|
|
545
|
+
): Promise<void> => {
|
|
546
|
+
let found: { deckId: string; button: RuntimeDeck["buttons"][number] } | null
|
|
547
|
+
try {
|
|
548
|
+
found = findButton(buttonId)
|
|
549
|
+
} catch (err) {
|
|
550
|
+
// ponytail: findButton indexes into a Map — should never throw, but
|
|
551
|
+
// a corrupted deck entry would otherwise crash the daemon on every
|
|
552
|
+
// gesture. Log + bail; let the next gesture try again.
|
|
553
|
+
logger.error({ err, buttonId }, "runtime: findButton threw")
|
|
554
|
+
return
|
|
555
|
+
}
|
|
556
|
+
if (found === null) {
|
|
557
|
+
logger.warn({ buttonId }, "invokeAction: button not found")
|
|
558
|
+
return
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
try {
|
|
562
|
+
if (handleSystemButton(found.button.type, gesture)) {
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (lockActive && found.deckId === "core:lock") {
|
|
567
|
+
if (LOCK_FOLDER_NAV_TYPES.has(found.button.type)) {
|
|
568
|
+
lockActive = false
|
|
569
|
+
preLockActiveDeckId = null
|
|
570
|
+
preLockOverlayDeckId = null
|
|
571
|
+
if (overlayDeckId !== null) setOverlay(null)
|
|
572
|
+
pubSub.publish("runtime:lock-mode", {
|
|
573
|
+
active: false,
|
|
574
|
+
reason: "escape",
|
|
575
|
+
})
|
|
576
|
+
logger.info(
|
|
577
|
+
{ buttonId, gesture, buttonType: found.button.type },
|
|
578
|
+
"runtime: lock escaped via folder-nav button",
|
|
579
|
+
)
|
|
580
|
+
} else {
|
|
581
|
+
logger.debug(
|
|
582
|
+
{ buttonId, gesture, buttonType: found.button.type },
|
|
583
|
+
"runtime: lock-mode gesture suppressed",
|
|
584
|
+
)
|
|
585
|
+
return
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (found.deckId !== getActiveDeckId() && found.deckId !== "core:lock") {
|
|
590
|
+
logger.debug(
|
|
591
|
+
{ buttonId, deckId: found.deckId, activeDeckId: getActiveDeckId() },
|
|
592
|
+
"invokeAction: gesture on inactive deck, dropping",
|
|
593
|
+
)
|
|
594
|
+
return
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const userAction =
|
|
598
|
+
gesture === "tap"
|
|
599
|
+
? found.button.actions?.tap
|
|
600
|
+
: gesture === "dbl-tap"
|
|
601
|
+
? found.button.actions?.dbltap
|
|
602
|
+
: found.button.actions?.hold
|
|
603
|
+
|
|
604
|
+
const deck = deckById(found.deckId)
|
|
605
|
+
// ponytail: Array.isArray is tighter than — rejects
|
|
606
|
+
// , , and non-array values uniformly. Defends
|
|
607
|
+
// against any RuntimeDeck that ends up with missing
|
|
608
|
+
// (corrupted state, future refactor, runtime injection).
|
|
609
|
+
const position =
|
|
610
|
+
found.button.position ??
|
|
611
|
+
(Array.isArray(deck?.buttons)
|
|
612
|
+
? deck.buttons.findIndex((b) => b.id === found.button.id)
|
|
613
|
+
: -1)
|
|
614
|
+
|
|
615
|
+
logger.info(
|
|
616
|
+
{
|
|
617
|
+
buttonId,
|
|
618
|
+
deckId: found.deckId,
|
|
619
|
+
position,
|
|
620
|
+
gesture,
|
|
621
|
+
buttonType: found.button.type,
|
|
622
|
+
buttonActions: found.button.actions,
|
|
623
|
+
userAction,
|
|
624
|
+
},
|
|
625
|
+
"[runtime] invokeAction resolved",
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
if (userAction !== undefined) {
|
|
629
|
+
logger.info(
|
|
630
|
+
{ buttonId, gesture, action: userAction },
|
|
631
|
+
"[addon:sirenodeck] user action",
|
|
632
|
+
)
|
|
633
|
+
const capability = getRequiredCapability(userAction)
|
|
634
|
+
if (capability !== null && !getMethods().checkRequirement(capability)) {
|
|
635
|
+
logger.warn(
|
|
636
|
+
{ buttonId, gesture, action: userAction, capability },
|
|
637
|
+
"[runtime] action skipped: missing system requirement",
|
|
638
|
+
)
|
|
639
|
+
if (position >= 0) {
|
|
640
|
+
getMethods().showTemporaryError(
|
|
641
|
+
found.deckId,
|
|
642
|
+
position,
|
|
643
|
+
undefined,
|
|
644
|
+
found.button.id,
|
|
645
|
+
`missing-requirement: ${capability}`,
|
|
646
|
+
)
|
|
647
|
+
}
|
|
648
|
+
return
|
|
649
|
+
}
|
|
650
|
+
try {
|
|
651
|
+
await getMethods().dispatch(userAction)
|
|
652
|
+
} catch (err) {
|
|
653
|
+
logger.error(
|
|
654
|
+
{ buttonId, gesture, err },
|
|
655
|
+
"[addon:sirenodeck] user action failed",
|
|
656
|
+
)
|
|
657
|
+
if (position >= 0) {
|
|
658
|
+
getMethods().showTemporaryError(
|
|
659
|
+
found.deckId,
|
|
660
|
+
position,
|
|
661
|
+
undefined,
|
|
662
|
+
found.button.id,
|
|
663
|
+
`action-failed: ${userAction}`,
|
|
664
|
+
)
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const handlerKey = `${found.deckId}:${found.button.id}`
|
|
671
|
+
const handler = handlers.get(handlerKey)
|
|
672
|
+
if (handler === undefined) {
|
|
673
|
+
// Dynamic decks can be rebuilt after addon services mount. Pagination
|
|
674
|
+
// is declarative, so keep its navigation working for those new buttons.
|
|
675
|
+
if (found.button.type === "core:page-nav") {
|
|
676
|
+
const config = found.button.config
|
|
677
|
+
if (typeof config === "object" && config !== null) {
|
|
678
|
+
const target =
|
|
679
|
+
gesture === "tap"
|
|
680
|
+
? (config as { nextDeckId?: unknown }).nextDeckId
|
|
681
|
+
: gesture === "hold"
|
|
682
|
+
? (config as { prevDeckId?: unknown }).prevDeckId
|
|
683
|
+
: undefined
|
|
684
|
+
if (typeof target === "string") {
|
|
685
|
+
navigateToDeck(target, { addToHistory: false })
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return
|
|
690
|
+
}
|
|
691
|
+
const ctx: ButtonActionContext = {
|
|
692
|
+
buttonId,
|
|
693
|
+
deckId: found.deckId,
|
|
694
|
+
config: found.button.config,
|
|
695
|
+
gesture,
|
|
696
|
+
position: found.button.position,
|
|
697
|
+
}
|
|
698
|
+
const fn =
|
|
699
|
+
gesture === "tap"
|
|
700
|
+
? handler.onTap
|
|
701
|
+
: gesture === "dbl-tap"
|
|
702
|
+
? handler.onDblTap
|
|
703
|
+
: handler.onHold
|
|
704
|
+
if (fn === undefined) {
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
await fn(ctx)
|
|
708
|
+
} catch (err) {
|
|
709
|
+
// ponytail: a single bad button-press must not crash the entire
|
|
710
|
+
// daemon. Log the failure with full context so operators see it in
|
|
711
|
+
// service.log and can file a bug — but keep serving subsequent
|
|
712
|
+
// gestures instead of exiting via killChildrenAndExit.
|
|
713
|
+
logger.error(
|
|
714
|
+
{
|
|
715
|
+
err,
|
|
716
|
+
buttonId,
|
|
717
|
+
gesture,
|
|
718
|
+
deckId: found.deckId,
|
|
719
|
+
buttonType: found.button.type,
|
|
720
|
+
},
|
|
721
|
+
"invokeAction: uncaught error in handler",
|
|
722
|
+
)
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const dispatchGesture = async (
|
|
727
|
+
buttonId: string,
|
|
728
|
+
gesture: GestureKind,
|
|
729
|
+
): Promise<void> => {
|
|
730
|
+
const found = findButton(buttonId)
|
|
731
|
+
if (found === null) {
|
|
732
|
+
logger.warn({ buttonId }, "dispatchGesture: button not found")
|
|
733
|
+
return
|
|
734
|
+
}
|
|
735
|
+
gestureListener?.(found.button.id, {
|
|
736
|
+
gesture,
|
|
737
|
+
at: Date.now(),
|
|
738
|
+
position: found.button.position,
|
|
739
|
+
})
|
|
740
|
+
await invokeAction(buttonId, gesture)
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const setGestureListener = (listener: GestureListener | null): void => {
|
|
744
|
+
gestureListener = listener
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const invalidate = (): void => {
|
|
748
|
+
pubSub.publish("runtime:invalidate", { activeDeckId: getActiveDeckId() })
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
void store
|
|
752
|
+
|
|
753
|
+
const overlayDecks = (): Array<{
|
|
754
|
+
deck: RuntimeDeck
|
|
755
|
+
matcher: ReturnType<typeof compileDeckMatcher>
|
|
756
|
+
specificity: number
|
|
757
|
+
}> => {
|
|
758
|
+
const result: Array<{
|
|
759
|
+
deck: RuntimeDeck
|
|
760
|
+
matcher: ReturnType<typeof compileDeckMatcher>
|
|
761
|
+
specificity: number
|
|
762
|
+
}> = []
|
|
763
|
+
for (const deck of decks) {
|
|
764
|
+
const hasProcess =
|
|
765
|
+
deck.processNames !== undefined && deck.processNames.length > 0
|
|
766
|
+
const hasWindow =
|
|
767
|
+
deck.windowNames !== undefined && deck.windowNames.length > 0
|
|
768
|
+
if (!hasProcess && !hasWindow) continue
|
|
769
|
+
result.push({
|
|
770
|
+
deck,
|
|
771
|
+
matcher: compileDeckMatcher({
|
|
772
|
+
processNames: deck.processNames,
|
|
773
|
+
windowNames: deck.windowNames,
|
|
774
|
+
}),
|
|
775
|
+
specificity: (hasProcess ? 1 : 0) + (hasWindow ? 1 : 0),
|
|
776
|
+
})
|
|
777
|
+
}
|
|
778
|
+
return result
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
let activeAppPoll: ReturnType<typeof setInterval> | null = null
|
|
782
|
+
let activeAppProvider: ActiveAppProviderLike | null = null
|
|
783
|
+
let lastOverlayDeckId: string | null = overlayDeckId
|
|
784
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
785
|
+
let pendingOverlayDeckId: string | null = null
|
|
786
|
+
let latestActiveAppSnapshot: {
|
|
787
|
+
name: string
|
|
788
|
+
windowTitle: string | null
|
|
789
|
+
processId: number | null
|
|
790
|
+
} | null = null
|
|
791
|
+
|
|
792
|
+
const applyOverlay = (deckId: string | null): void => {
|
|
793
|
+
if (deckId !== null && deckById(deckId) === undefined) {
|
|
794
|
+
logger.warn({ deckId }, "active-app: overlay deck not found")
|
|
795
|
+
return
|
|
796
|
+
}
|
|
797
|
+
if (deckId === null) {
|
|
798
|
+
// ponytail: no overlay matches anymore. If we had one active, dismiss it.
|
|
799
|
+
if (overlayDeckId !== null) {
|
|
800
|
+
logger.info(
|
|
801
|
+
{ prevOverlayId: overlayDeckId },
|
|
802
|
+
"active-app: dismissing previous overlay (no match)",
|
|
803
|
+
)
|
|
804
|
+
setOverlay(null, { source: "autoShow" })
|
|
805
|
+
}
|
|
806
|
+
lastOverlayDeckId = null
|
|
807
|
+
return
|
|
808
|
+
}
|
|
809
|
+
if (overlayDeckId !== null && deckId !== overlayDeckId) {
|
|
810
|
+
// ponytail: overlay mode is a routing branch. If the new match is
|
|
811
|
+
// auto-show it replaces the current overlay; if it's not auto-show the
|
|
812
|
+
// current overlay dismisses and the new match stays as available-only
|
|
813
|
+
// (manual toggle required). In both cases the previous overlay's
|
|
814
|
+
// overlayNavStacks entry is preserved for re-activation.
|
|
815
|
+
const newDeck = deckById(deckId)
|
|
816
|
+
if (newDeck === undefined) return
|
|
817
|
+
if (newDeck.autoShow === true) {
|
|
818
|
+
logger.info(
|
|
819
|
+
{ prevOverlayId: overlayDeckId, newMatch: deckId },
|
|
820
|
+
"active-app: switching overlay (match moved, autoShow)",
|
|
821
|
+
)
|
|
822
|
+
setOverlay(deckId, { source: "autoShow" })
|
|
823
|
+
} else {
|
|
824
|
+
logger.info(
|
|
825
|
+
{ prevOverlayId: overlayDeckId, newMatch: deckId },
|
|
826
|
+
"active-app: dismissing current overlay (new match is not autoShow)",
|
|
827
|
+
)
|
|
828
|
+
setOverlay(null, { source: "autoShow" })
|
|
829
|
+
}
|
|
830
|
+
lastOverlayDeckId = deckId
|
|
831
|
+
return
|
|
832
|
+
}
|
|
833
|
+
if (deckId === lastOverlayDeckId) return
|
|
834
|
+
const deck = deckById(deckId)
|
|
835
|
+
if (deck === undefined) return
|
|
836
|
+
logger.info(
|
|
837
|
+
{ deckId, autoShow: deck.autoShow === true },
|
|
838
|
+
"active-app: applying overlay",
|
|
839
|
+
)
|
|
840
|
+
if (deck.autoShow !== true) {
|
|
841
|
+
lastOverlayDeckId = deckId
|
|
842
|
+
return
|
|
843
|
+
}
|
|
844
|
+
lastOverlayDeckId = deckId
|
|
845
|
+
setOverlay(deckId, { source: "autoShow" })
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const computeOverlayFor = (snapshot: {
|
|
849
|
+
name: string
|
|
850
|
+
windowTitle: string | null
|
|
851
|
+
processId: number | null
|
|
852
|
+
}): string | null => {
|
|
853
|
+
let bestId: string | null = null
|
|
854
|
+
let bestSpecificity = -1
|
|
855
|
+
for (const { deck, matcher, specificity } of overlayDecks()) {
|
|
856
|
+
if (!matcher(snapshot)) continue
|
|
857
|
+
if (specificity > bestSpecificity) {
|
|
858
|
+
bestId = deck.id
|
|
859
|
+
bestSpecificity = specificity
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (availableOverlayDeckId !== bestId) {
|
|
863
|
+
const prev = availableOverlayDeckId
|
|
864
|
+
availableOverlayDeckId = bestId
|
|
865
|
+
if (bestId !== null) {
|
|
866
|
+
logger.info(
|
|
867
|
+
{
|
|
868
|
+
from: prev,
|
|
869
|
+
to: bestId,
|
|
870
|
+
snapshot: {
|
|
871
|
+
name: snapshot.name,
|
|
872
|
+
windowTitle: snapshot.windowTitle,
|
|
873
|
+
},
|
|
874
|
+
},
|
|
875
|
+
"active-app: overlay deck available",
|
|
876
|
+
)
|
|
877
|
+
} else if (prev !== null) {
|
|
878
|
+
logger.info({ from: prev }, "active-app: no overlay deck matches")
|
|
879
|
+
}
|
|
880
|
+
pubSub.publish("runtime:overlay-available", { deckId: bestId })
|
|
881
|
+
}
|
|
882
|
+
return bestId
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const scheduleOverlay = (deckId: string | null): void => {
|
|
886
|
+
pendingOverlayDeckId = deckId
|
|
887
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer)
|
|
888
|
+
debounceTimer = setTimeout(() => {
|
|
889
|
+
debounceTimer = null
|
|
890
|
+
const next = pendingOverlayDeckId
|
|
891
|
+
pendingOverlayDeckId = null
|
|
892
|
+
applyOverlay(next)
|
|
893
|
+
}, 200)
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const startActiveAppLoop = (provider: ActiveAppProviderLike): void => {
|
|
897
|
+
if (activeAppPoll !== null) return
|
|
898
|
+
logger.info("active-app: poll loop started")
|
|
899
|
+
activeAppPoll = setInterval(() => {
|
|
900
|
+
void provider.getActive().then((snapshot) => {
|
|
901
|
+
if (snapshot === null) {
|
|
902
|
+
latestActiveAppSnapshot = null
|
|
903
|
+
scheduleOverlay(null)
|
|
904
|
+
return
|
|
905
|
+
}
|
|
906
|
+
latestActiveAppSnapshot = snapshot
|
|
907
|
+
logger.debug(
|
|
908
|
+
{
|
|
909
|
+
snapshot: {
|
|
910
|
+
name: snapshot.name,
|
|
911
|
+
windowTitle: snapshot.windowTitle,
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
"active-app: snapshot",
|
|
915
|
+
)
|
|
916
|
+
scheduleOverlay(computeOverlayFor(snapshot))
|
|
917
|
+
})
|
|
918
|
+
}, 1000)
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const stopActiveAppLoop = (): void => {
|
|
922
|
+
if (debounceTimer !== null) {
|
|
923
|
+
clearTimeout(debounceTimer)
|
|
924
|
+
debounceTimer = null
|
|
925
|
+
}
|
|
926
|
+
pendingOverlayDeckId = null
|
|
927
|
+
if (activeAppPoll !== null) {
|
|
928
|
+
clearInterval(activeAppPoll)
|
|
929
|
+
activeAppPoll = null
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const setActiveAppProvider = (provider: ActiveAppProviderLike): void => {
|
|
934
|
+
activeAppProvider = provider
|
|
935
|
+
startActiveAppLoop(provider)
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const setSessionProvider = (provider: SessionProvider): void => {
|
|
939
|
+
if (sessionUnsubscribe !== null) {
|
|
940
|
+
sessionUnsubscribe()
|
|
941
|
+
sessionUnsubscribe = null
|
|
942
|
+
}
|
|
943
|
+
const handle = (state: SessionState): void => {
|
|
944
|
+
if (state === "locked" && !lockActive) {
|
|
945
|
+
enterLockMode()
|
|
946
|
+
return
|
|
947
|
+
}
|
|
948
|
+
if (state === "locked" && lockActive) {
|
|
949
|
+
preLockActiveDeckId = snapshotRegularActiveDeckId()
|
|
950
|
+
preLockOverlayDeckId = overlayDeckId
|
|
951
|
+
logger.info("runtime: lock snapshot refreshed on re-lock")
|
|
952
|
+
return
|
|
953
|
+
}
|
|
954
|
+
if (state === "unlocked" && lockActive) {
|
|
955
|
+
lockActive = false
|
|
956
|
+
const overlaySnapshot = preLockOverlayDeckId
|
|
957
|
+
const activeSnapshot = preLockActiveDeckId
|
|
958
|
+
preLockActiveDeckId = null
|
|
959
|
+
preLockOverlayDeckId = null
|
|
960
|
+
if (
|
|
961
|
+
overlaySnapshot !== null &&
|
|
962
|
+
latestActiveAppSnapshot !== null &&
|
|
963
|
+
computeOverlayFor(latestActiveAppSnapshot) === overlaySnapshot
|
|
964
|
+
) {
|
|
965
|
+
logger.info(
|
|
966
|
+
{ overlayId: overlaySnapshot },
|
|
967
|
+
"runtime: overlay auto-resumed on unlock (trigger still matches)",
|
|
968
|
+
)
|
|
969
|
+
setOverlay(overlaySnapshot, { source: "autoShow" })
|
|
970
|
+
} else {
|
|
971
|
+
if (overlayDeckId !== null) setOverlay(null)
|
|
972
|
+
const restoreId = activeSnapshot ?? mainDeck.id
|
|
973
|
+
logger.info(
|
|
974
|
+
{ restoreId, overlayWas: overlaySnapshot, triggerMatches: false },
|
|
975
|
+
"runtime: lock cleared, restoring previous deck",
|
|
976
|
+
)
|
|
977
|
+
navigateToDeck(restoreId, { addToHistory: false })
|
|
978
|
+
}
|
|
979
|
+
pubSub.publish("runtime:lock-mode", {
|
|
980
|
+
active: false,
|
|
981
|
+
reason: "session-unlocked",
|
|
982
|
+
})
|
|
983
|
+
pubSub.publish("runtime:invalidate", undefined)
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
sessionUnsubscribe = provider.subscribe(handle)
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const stopActiveAppPolling = async (): Promise<void> => {
|
|
990
|
+
stopActiveAppLoop()
|
|
991
|
+
if (activeAppProvider !== null) {
|
|
992
|
+
try {
|
|
993
|
+
await activeAppProvider.stop()
|
|
994
|
+
} catch (err) {
|
|
995
|
+
logger.warn({ err }, "active-app: provider stop() failed")
|
|
996
|
+
}
|
|
997
|
+
activeAppProvider = null
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const runtime: Runtime = {
|
|
1002
|
+
getActiveDeck,
|
|
1003
|
+
getActiveDeckId,
|
|
1004
|
+
deckExists: (id: string) =>
|
|
1005
|
+
deckById(id) !== undefined || deckById(`${id}-p1`) !== undefined,
|
|
1006
|
+
setDecks,
|
|
1007
|
+
navigateToDeck,
|
|
1008
|
+
goBack,
|
|
1009
|
+
setOverlay,
|
|
1010
|
+
getOverlay,
|
|
1011
|
+
registerButtonHandler,
|
|
1012
|
+
unregisterButtonHandler,
|
|
1013
|
+
mountAddonButtons,
|
|
1014
|
+
dispatchGesture,
|
|
1015
|
+
invokeAction,
|
|
1016
|
+
setGestureListener,
|
|
1017
|
+
invalidate,
|
|
1018
|
+
setActiveAppProvider,
|
|
1019
|
+
setSessionProvider,
|
|
1020
|
+
isLockActive: () => lockActive,
|
|
1021
|
+
stopActiveAppPolling,
|
|
1022
|
+
navStackDepth: () => navStack.length,
|
|
1023
|
+
hasOverlayDeckAvailable: () =>
|
|
1024
|
+
availableOverlayDeckId !== null || pendingOverlayDeckId !== null,
|
|
1025
|
+
getAvailableOverlayDeckIcon: (): string | null => {
|
|
1026
|
+
const id = availableOverlayDeckId ?? pendingOverlayDeckId
|
|
1027
|
+
if (id === null) return null
|
|
1028
|
+
const deck = deckById(id)
|
|
1029
|
+
return deck?.icon ?? null
|
|
1030
|
+
},
|
|
1031
|
+
getAvailableOverlayDeckName: (): string | null => {
|
|
1032
|
+
const id = availableOverlayDeckId ?? pendingOverlayDeckId
|
|
1033
|
+
if (id === null) return null
|
|
1034
|
+
const deck = deckById(id)
|
|
1035
|
+
return deck?.name ?? null
|
|
1036
|
+
},
|
|
1037
|
+
getBrightness: () => brightness,
|
|
1038
|
+
setBrightness: (value: number) => {
|
|
1039
|
+
const clamped = Math.max(10, Math.min(100, Math.round(value)))
|
|
1040
|
+
if (clamped === brightness) return
|
|
1041
|
+
brightness = clamped
|
|
1042
|
+
pubSub.publish("sireno:settings:brightness", { value: clamped })
|
|
1043
|
+
},
|
|
1044
|
+
getEditorSurfaces: ({ keyCount = 15, revision = 0 } = {}) => {
|
|
1045
|
+
return describeEditorSurfaces({
|
|
1046
|
+
decks,
|
|
1047
|
+
keyCount,
|
|
1048
|
+
revision,
|
|
1049
|
+
systemState: {
|
|
1050
|
+
navStackDepth: navStack.length,
|
|
1051
|
+
hasOverlayDeckAvailable: availableOverlayDeckId !== null,
|
|
1052
|
+
lockActive,
|
|
1053
|
+
inOverlayMode: overlayDeckId !== null,
|
|
1054
|
+
},
|
|
1055
|
+
})
|
|
1056
|
+
},
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
return runtime
|
|
1060
|
+
}
|