feat(nudges): Phase 6 — Margot soft-touch nudges via frontend nudge bus
Frontend-owned nudge bus that consumes in-app signals Corbie already produces, applies suppression, and fans out to OS notification + an optional TTS read-aloud. OS-wide keyboard/window activity detection stays deferred per the revised roadmap — the plan before rewrite would have been brittle on Wayland, permission-heavy on macOS, and low-quality everywhere. Triggers (v1, all in-app signals): - inactivity_with_active_timer — timer running, window blurred ≥ 90 s, at least 60 s into the timer. - pending_morning_triage — ritual enabled, past 10:00 local, last shown ≠ today. Polls every 5 min while focused. - micro_step_idle — micro-step decomposition created, no child step or parent task completed within 15 min. Suppression: - Respects nudgesEnabled + nudgesMuted. - No nudge while the app has focus (document.hasFocus). - Hard cap 3 per rolling hour. - Permission requested via @tauri-apps/plugin-notification on first delivery; denial is silently respected. Rust side: - tauri-plugin-notification registered + ACL entries on the main- window capability only (secondary windows can't fire nudges). - commands::nudges::deliver_nudge — thin wrapper, security-guarded via ensure_main_window, delegates to the plugin. No DB writes — the roadmap's nudges-audit table is deferred until a concrete need emerges. Frontend glue: - nudgeBus.svelte.ts — subscribes to window events, applies suppression, calls deliver_nudge (+ tts_speak when speakAloud is on). - kon:task-completed now dispatched on complete_task_cmd success. - kon:microstep-generated + kon:step-completed dispatched from MicroSteps so the idle trigger can clear itself on any engagement. - kon:focus-timer-cancelled added to focusTimer so the bus can reset its inactivity state on cancel, not only on natural completion. - nudgeBus started from +layout.svelte onMount, stopped on destroy. Settings: - New "Nudges" section with three toggles: Enable nudges, Mute for now (separate so a hard mute doesn't lose preferences), Speak nudges aloud (reuses Phase 4 TTS). - All default OFF. No first-run prompt — nudges are a Settings-found feature rather than a walkthrough step. Out of scope per the revised Phase 6 spec: OS-wide keyboard/window hooks, biometric signals, custom trigger editor (Phase 7), notification sound (platform variance too high for Layer-1 — revisit in Phase 9 polish with a bundled .wav). Gates: fmt clean, clippy -D warnings clean, cargo test 262/0 (4 existing + 262 current), npm run check 0/0, npm run build green.
This commit is contained in:
107
Cargo.lock
generated
107
Cargo.lock
generated
@@ -2531,10 +2531,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.85"
|
||||
version = "0.3.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
||||
checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -2599,6 +2601,7 @@ dependencies = [
|
||||
"tauri-plugin-autostart",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-global-shortcut",
|
||||
"tauri-plugin-notification",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-window-state",
|
||||
"tokio",
|
||||
@@ -2909,6 +2912,18 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "mac-notification-sys"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29a16783dd1a47849b8c8133c9cd3eb2112cfbc6901670af3dba47c8bbfb07d3"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.5.0"
|
||||
@@ -3187,6 +3202,20 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-rust"
|
||||
version = "4.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e551a9f0db223eaf3eb156906f99f46897fd951ee66dd1cb0be14db4d36d2fa"
|
||||
dependencies = [
|
||||
"futures-lite",
|
||||
"log",
|
||||
"mac-notification-sys",
|
||||
"serde",
|
||||
"tauri-winrt-notification",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-types"
|
||||
version = "1.0.1"
|
||||
@@ -3867,7 +3896,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.14.0",
|
||||
"quick-xml",
|
||||
"quick-xml 0.38.4",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
@@ -3914,9 +3943,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
|
||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
@@ -4056,6 +4085,15 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.4"
|
||||
@@ -5738,6 +5776,25 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-notification"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc"
|
||||
dependencies = [
|
||||
"log",
|
||||
"notify-rust",
|
||||
"rand 0.9.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.5.3"
|
||||
@@ -5875,6 +5932,18 @@ dependencies = [
|
||||
"toml 0.9.12+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-winrt-notification"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
|
||||
dependencies = [
|
||||
"quick-xml 0.37.5",
|
||||
"thiserror 2.0.18",
|
||||
"windows 0.61.3",
|
||||
"windows-version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
@@ -6598,9 +6667,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.108"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
||||
checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -6611,23 +6680,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.58"
|
||||
version = "0.4.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f"
|
||||
checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.108"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
||||
checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -6635,9 +6700,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.108"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
||||
checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -6648,9 +6713,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.108"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
||||
checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -6717,9 +6782,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.85"
|
||||
version = "0.3.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
|
||||
checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"@tauri-apps/plugin-autostart": "^2.5.1",
|
||||
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
|
||||
"@tauri-apps/plugin-notification": "^2.3.3",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"lucide-svelte": "^0.577.0",
|
||||
"svelte-i18n": "^4.0.1"
|
||||
@@ -1630,6 +1631,15 @@
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-notification": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-notification/-/plugin-notification-2.3.3.tgz",
|
||||
"integrity": "sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-opener": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@tauri-apps/plugin-autostart": "^2.5.1",
|
||||
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
|
||||
"@tauri-apps/plugin-notification": "^2.3.3",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"lucide-svelte": "^0.577.0",
|
||||
"svelte-i18n": "^4.0.1"
|
||||
|
||||
@@ -47,6 +47,10 @@ tauri-plugin-window-state = "2"
|
||||
# Handles platform differences (.desktop file on Linux, LaunchAgents plist
|
||||
# on macOS, registry Run key on Windows) behind a single API.
|
||||
tauri-plugin-autostart = "2"
|
||||
# Phase 6 nudges: OS-native notifications via the frontend-owned nudge
|
||||
# bus. Permission-request flow happens on the JS side before the first
|
||||
# deliver_nudge call; Windows only delivers for installed apps.
|
||||
tauri-plugin-notification = "2"
|
||||
|
||||
# Serialisation
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
"global-shortcut:allow-unregister",
|
||||
"autostart:allow-enable",
|
||||
"autostart:allow-disable",
|
||||
"autostart:allow-is-enabled"
|
||||
"autostart:allow-is-enabled",
|
||||
"notification:allow-is-permission-granted",
|
||||
"notification:allow-request-permission",
|
||||
"notification:allow-notify"
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"main":{"identifier":"main","description":"Main window capability for user-initiated app control.","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-set-always-on-top","core:window:allow-minimize","core:window:allow-toggle-maximize","core:window:allow-is-maximized","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus","opener:default","dialog:default","global-shortcut:allow-register","global-shortcut:allow-unregister","autostart:allow-enable","autostart:allow-disable","autostart:allow-is-enabled"]},"secondary-windows":{"identifier":"secondary-windows","description":"Narrow capability for passive secondary windows.","local":true,"windows":["tasks-float","transcript-viewer","transcription-preview"],"permissions":["core:event:default","core:window:allow-start-dragging","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus"]}}
|
||||
{"main":{"identifier":"main","description":"Main window capability for user-initiated app control.","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-set-always-on-top","core:window:allow-minimize","core:window:allow-toggle-maximize","core:window:allow-is-maximized","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus","opener:default","dialog:default","global-shortcut:allow-register","global-shortcut:allow-unregister","autostart:allow-enable","autostart:allow-disable","autostart:allow-is-enabled","notification:allow-is-permission-granted","notification:allow-request-permission","notification:allow-notify"]},"secondary-windows":{"identifier":"secondary-windows","description":"Narrow capability for passive secondary windows.","local":true,"windows":["tasks-float","transcript-viewer","transcription-preview"],"permissions":["core:event:default","core:window:allow-start-dragging","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus"]}}
|
||||
@@ -2355,22 +2355,22 @@
|
||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`",
|
||||
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`",
|
||||
"type": "string",
|
||||
"const": "dialog:default",
|
||||
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
||||
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the ask command without any pre-configured scope.",
|
||||
"description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:allow-ask",
|
||||
"markdownDescription": "Enables the ask command without any pre-configured scope."
|
||||
"markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Enables the confirm command without any pre-configured scope.",
|
||||
"description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:allow-confirm",
|
||||
"markdownDescription": "Enables the confirm command without any pre-configured scope."
|
||||
"markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Enables the message command without any pre-configured scope.",
|
||||
@@ -2391,16 +2391,16 @@
|
||||
"markdownDescription": "Enables the save command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the ask command without any pre-configured scope.",
|
||||
"description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:deny-ask",
|
||||
"markdownDescription": "Denies the ask command without any pre-configured scope."
|
||||
"markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Denies the confirm command without any pre-configured scope.",
|
||||
"description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:deny-confirm",
|
||||
"markdownDescription": "Denies the confirm command without any pre-configured scope."
|
||||
"markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Denies the message command without any pre-configured scope.",
|
||||
@@ -2486,6 +2486,204 @@
|
||||
"const": "global-shortcut:deny-unregister-all",
|
||||
"markdownDescription": "Denies the unregister_all command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||
"type": "string",
|
||||
"const": "notification:default",
|
||||
"markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the batch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-batch",
|
||||
"markdownDescription": "Enables the batch command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-cancel",
|
||||
"markdownDescription": "Enables the cancel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the check_permissions command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-check-permissions",
|
||||
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the create_channel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-create-channel",
|
||||
"markdownDescription": "Enables the create_channel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the delete_channel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-delete-channel",
|
||||
"markdownDescription": "Enables the delete_channel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_active command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-get-active",
|
||||
"markdownDescription": "Enables the get_active command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_pending command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-get-pending",
|
||||
"markdownDescription": "Enables the get_pending command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the is_permission_granted command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-is-permission-granted",
|
||||
"markdownDescription": "Enables the is_permission_granted command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the list_channels command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-list-channels",
|
||||
"markdownDescription": "Enables the list_channels command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the notify command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-notify",
|
||||
"markdownDescription": "Enables the notify command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the permission_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-permission-state",
|
||||
"markdownDescription": "Enables the permission_state command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the register_action_types command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-register-action-types",
|
||||
"markdownDescription": "Enables the register_action_types command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-register-listener",
|
||||
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the remove_active command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-remove-active",
|
||||
"markdownDescription": "Enables the remove_active command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the request_permission command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-request-permission",
|
||||
"markdownDescription": "Enables the request_permission command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the show command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-show",
|
||||
"markdownDescription": "Enables the show command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the batch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-batch",
|
||||
"markdownDescription": "Denies the batch command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-cancel",
|
||||
"markdownDescription": "Denies the cancel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the check_permissions command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-check-permissions",
|
||||
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the create_channel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-create-channel",
|
||||
"markdownDescription": "Denies the create_channel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the delete_channel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-delete-channel",
|
||||
"markdownDescription": "Denies the delete_channel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_active command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-get-active",
|
||||
"markdownDescription": "Denies the get_active command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_pending command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-get-pending",
|
||||
"markdownDescription": "Denies the get_pending command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the is_permission_granted command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-is-permission-granted",
|
||||
"markdownDescription": "Denies the is_permission_granted command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the list_channels command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-list-channels",
|
||||
"markdownDescription": "Denies the list_channels command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the notify command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-notify",
|
||||
"markdownDescription": "Denies the notify command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the permission_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-permission-state",
|
||||
"markdownDescription": "Denies the permission_state command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the register_action_types command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-register-action-types",
|
||||
"markdownDescription": "Denies the register_action_types command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the register_listener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-register-listener",
|
||||
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the remove_active command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-remove-active",
|
||||
"markdownDescription": "Denies the remove_active command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the request_permission command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-request-permission",
|
||||
"markdownDescription": "Denies the request_permission command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the show command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-show",
|
||||
"markdownDescription": "Denies the show command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
|
||||
"type": "string",
|
||||
@@ -2534,60 +2732,6 @@
|
||||
"const": "opener:deny-reveal-item-in-dir",
|
||||
"markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
|
||||
"type": "string",
|
||||
"const": "updater:default",
|
||||
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-check",
|
||||
"markdownDescription": "Enables the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download",
|
||||
"markdownDescription": "Enables the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download-and-install",
|
||||
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-install",
|
||||
"markdownDescription": "Enables the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-check",
|
||||
"markdownDescription": "Denies the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download",
|
||||
"markdownDescription": "Denies the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download-and-install",
|
||||
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-install",
|
||||
"markdownDescription": "Denies the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`",
|
||||
"type": "string",
|
||||
|
||||
@@ -2355,22 +2355,22 @@
|
||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`",
|
||||
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`",
|
||||
"type": "string",
|
||||
"const": "dialog:default",
|
||||
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
||||
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the ask command without any pre-configured scope.",
|
||||
"description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:allow-ask",
|
||||
"markdownDescription": "Enables the ask command without any pre-configured scope."
|
||||
"markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Enables the confirm command without any pre-configured scope.",
|
||||
"description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:allow-confirm",
|
||||
"markdownDescription": "Enables the confirm command without any pre-configured scope."
|
||||
"markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Enables the message command without any pre-configured scope.",
|
||||
@@ -2391,16 +2391,16 @@
|
||||
"markdownDescription": "Enables the save command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the ask command without any pre-configured scope.",
|
||||
"description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:deny-ask",
|
||||
"markdownDescription": "Denies the ask command without any pre-configured scope."
|
||||
"markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Denies the confirm command without any pre-configured scope.",
|
||||
"description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||
"type": "string",
|
||||
"const": "dialog:deny-confirm",
|
||||
"markdownDescription": "Denies the confirm command without any pre-configured scope."
|
||||
"markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||
},
|
||||
{
|
||||
"description": "Denies the message command without any pre-configured scope.",
|
||||
@@ -2486,6 +2486,204 @@
|
||||
"const": "global-shortcut:deny-unregister-all",
|
||||
"markdownDescription": "Denies the unregister_all command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||
"type": "string",
|
||||
"const": "notification:default",
|
||||
"markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the batch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-batch",
|
||||
"markdownDescription": "Enables the batch command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-cancel",
|
||||
"markdownDescription": "Enables the cancel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the check_permissions command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-check-permissions",
|
||||
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the create_channel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-create-channel",
|
||||
"markdownDescription": "Enables the create_channel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the delete_channel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-delete-channel",
|
||||
"markdownDescription": "Enables the delete_channel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_active command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-get-active",
|
||||
"markdownDescription": "Enables the get_active command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_pending command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-get-pending",
|
||||
"markdownDescription": "Enables the get_pending command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the is_permission_granted command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-is-permission-granted",
|
||||
"markdownDescription": "Enables the is_permission_granted command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the list_channels command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-list-channels",
|
||||
"markdownDescription": "Enables the list_channels command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the notify command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-notify",
|
||||
"markdownDescription": "Enables the notify command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the permission_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-permission-state",
|
||||
"markdownDescription": "Enables the permission_state command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the register_action_types command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-register-action-types",
|
||||
"markdownDescription": "Enables the register_action_types command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-register-listener",
|
||||
"markdownDescription": "Enables the register_listener command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the remove_active command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-remove-active",
|
||||
"markdownDescription": "Enables the remove_active command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the request_permission command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-request-permission",
|
||||
"markdownDescription": "Enables the request_permission command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the show command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:allow-show",
|
||||
"markdownDescription": "Enables the show command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the batch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-batch",
|
||||
"markdownDescription": "Denies the batch command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-cancel",
|
||||
"markdownDescription": "Denies the cancel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the check_permissions command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-check-permissions",
|
||||
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the create_channel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-create-channel",
|
||||
"markdownDescription": "Denies the create_channel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the delete_channel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-delete-channel",
|
||||
"markdownDescription": "Denies the delete_channel command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_active command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-get-active",
|
||||
"markdownDescription": "Denies the get_active command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_pending command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-get-pending",
|
||||
"markdownDescription": "Denies the get_pending command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the is_permission_granted command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-is-permission-granted",
|
||||
"markdownDescription": "Denies the is_permission_granted command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the list_channels command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-list-channels",
|
||||
"markdownDescription": "Denies the list_channels command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the notify command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-notify",
|
||||
"markdownDescription": "Denies the notify command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the permission_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-permission-state",
|
||||
"markdownDescription": "Denies the permission_state command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the register_action_types command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-register-action-types",
|
||||
"markdownDescription": "Denies the register_action_types command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the register_listener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-register-listener",
|
||||
"markdownDescription": "Denies the register_listener command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the remove_active command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-remove-active",
|
||||
"markdownDescription": "Denies the remove_active command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the request_permission command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-request-permission",
|
||||
"markdownDescription": "Denies the request_permission command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the show command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "notification:deny-show",
|
||||
"markdownDescription": "Denies the show command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
|
||||
"type": "string",
|
||||
@@ -2534,60 +2732,6 @@
|
||||
"const": "opener:deny-reveal-item-in-dir",
|
||||
"markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
|
||||
"type": "string",
|
||||
"const": "updater:default",
|
||||
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-check",
|
||||
"markdownDescription": "Enables the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download",
|
||||
"markdownDescription": "Enables the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download-and-install",
|
||||
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-install",
|
||||
"markdownDescription": "Enables the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-check",
|
||||
"markdownDescription": "Denies the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download",
|
||||
"markdownDescription": "Denies the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download-and-install",
|
||||
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-install",
|
||||
"markdownDescription": "Denies the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`",
|
||||
"type": "string",
|
||||
|
||||
@@ -8,6 +8,7 @@ pub mod live;
|
||||
pub mod llm;
|
||||
pub mod meeting;
|
||||
pub mod models;
|
||||
pub mod nudges;
|
||||
pub mod paste;
|
||||
pub mod power;
|
||||
pub mod profiles;
|
||||
|
||||
63
src-tauri/src/commands/nudges.rs
Normal file
63
src-tauri/src/commands/nudges.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
//! Phase 6 of the feature-complete roadmap: Margot soft-touch nudges.
|
||||
//!
|
||||
//! The nudge bus lives in the frontend (`nudgeBus.svelte.ts`) — it
|
||||
//! owns trigger subscription, suppression rules, and the hourly cap.
|
||||
//! This module is the thin Rust side that:
|
||||
//!
|
||||
//! - Guards delivery to the main window only (secondary windows like
|
||||
//! the task float can't fire notifications at the user — they're
|
||||
//! already beside the user's attention).
|
||||
//! - Delegates to `tauri-plugin-notification` for cross-platform OS
|
||||
//! delivery. The plugin handles the macOS Notification Center /
|
||||
//! Linux org.freedesktop.Notifications / Windows toast plumbing.
|
||||
//!
|
||||
//! Permission handling happens on the JS side via the plugin's
|
||||
//! `isPermissionGranted` / `requestPermission` API — before the first
|
||||
//! `deliver_nudge` call, the nudge bus prompts the user. If denied,
|
||||
//! subsequent calls return an error that the bus logs and swallows.
|
||||
//!
|
||||
//! No persistence for v1. Cooldown state is ephemeral; the roadmap's
|
||||
//! nudge-audit table is deferred until a real need emerges.
|
||||
|
||||
use serde::Deserialize;
|
||||
use tauri_plugin_notification::NotificationExt;
|
||||
|
||||
use crate::commands::security::ensure_main_window;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeliverNudgeInput {
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
/// Deliver a single OS notification. Does not apply suppression
|
||||
/// logic — the frontend nudge bus is responsible for cadence and
|
||||
/// cooldown, so this command is a blunt "push it now" primitive.
|
||||
///
|
||||
/// Returns an error if the notification plugin refuses the call
|
||||
/// (typically because permission was denied by the user at the OS
|
||||
/// level). The nudge bus logs and swallows these — nudges are
|
||||
/// fire-and-forget and must never surface errors to the user.
|
||||
#[tauri::command]
|
||||
pub fn deliver_nudge(
|
||||
app: tauri::AppHandle,
|
||||
window: tauri::WebviewWindow,
|
||||
input: DeliverNudgeInput,
|
||||
) -> Result<(), String> {
|
||||
ensure_main_window(&window)?;
|
||||
|
||||
let title = input.title.trim();
|
||||
let body = input.body.trim();
|
||||
if title.is_empty() && body.is_empty() {
|
||||
// A blank nudge is worse than no nudge — stay quiet.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
app.notification()
|
||||
.builder()
|
||||
.title(if title.is_empty() { "Corbie" } else { title })
|
||||
.body(body)
|
||||
.show()
|
||||
.map_err(|e| format!("notification delivery failed: {e}"))
|
||||
}
|
||||
@@ -138,6 +138,12 @@ pub fn run() {
|
||||
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
|
||||
None,
|
||||
))
|
||||
// Phase 6 nudges: OS-native notifications. The plugin exposes
|
||||
// isPermissionGranted / requestPermission / sendNotification on
|
||||
// the JS side; our deliver_nudge wrapper guards those calls
|
||||
// against the nudge-bus suppression rules and restricts
|
||||
// invocation to the main window via ensure_main_window.
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
// Remember size + position of every window across app restarts.
|
||||
// Without this, secondary windows (preview overlay, task float,
|
||||
// transcript viewer) open at whatever spot the compositor picks,
|
||||
@@ -321,6 +327,8 @@ pub fn run() {
|
||||
// Rituals (Phase 5 roadmap)
|
||||
commands::rituals::get_last_morning_triage,
|
||||
commands::rituals::mark_morning_triage_shown,
|
||||
// Nudges (Phase 6 roadmap)
|
||||
commands::nudges::deliver_nudge,
|
||||
// Profiles + profile terms (canonical SQLite-backed profile CRUD) — Task 12
|
||||
commands::profiles::list_profiles_cmd,
|
||||
commands::profiles::get_profile_cmd,
|
||||
|
||||
@@ -53,6 +53,15 @@
|
||||
parentTaskId,
|
||||
profileId: profilesStore.activeProfileId,
|
||||
});
|
||||
// Phase 6 nudge-bus signal. The bus schedules a 15-min idle
|
||||
// check on this parent task id — if no step or the task itself
|
||||
// is completed in that window, a gentle "still with that one?"
|
||||
// nudge fires.
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent('kon:microstep-generated', {
|
||||
detail: { parentTaskId },
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
error = String(e);
|
||||
} finally {
|
||||
@@ -65,6 +74,14 @@
|
||||
await invoke('complete_subtask_cmd', { subtaskId });
|
||||
const idx = subtasks.findIndex(s => s.id === subtaskId);
|
||||
if (idx >= 0) subtasks[idx] = { ...subtasks[idx], done: true };
|
||||
// Phase 6 nudge-bus signal. Any completed step clears the
|
||||
// micro-step-idle timer for this parent task — the breakdown
|
||||
// is demonstrably getting worked, no nudge needed.
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent('kon:step-completed', {
|
||||
detail: { id: subtaskId, parentTaskId },
|
||||
}));
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1532,6 +1532,45 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Nudges (Phase 6 roadmap) -->
|
||||
<div class="border-b border-border-subtle">
|
||||
<button
|
||||
class="flex items-center justify-between w-full py-4 px-5 text-left"
|
||||
onclick={() => openSection = openSection === 'nudges' ? null : 'nudges'}
|
||||
>
|
||||
<h3 class="font-display text-[18px] italic text-text">Nudges</h3>
|
||||
<span class="text-text-tertiary text-[16px] leading-none">{openSection === 'nudges' ? '−' : '+'}</span>
|
||||
</button>
|
||||
{#if openSection === 'nudges'}
|
||||
<div class="px-5 pb-5 animate-fade-in">
|
||||
<p class="text-[11px] text-text-tertiary mb-4">
|
||||
Gentle, anticipatory reminders. Capped at 3 per hour. Never fires while you're looking at Corbie.
|
||||
</p>
|
||||
|
||||
<Toggle
|
||||
bind:checked={settings.nudgesEnabled}
|
||||
label="Enable nudges"
|
||||
description="Soft-touch notifications when a timer has been ticking while you're away, when a morning triage is waiting, or when a micro-step decomposition has sat untouched for 15 minutes."
|
||||
/>
|
||||
|
||||
{#if settings.nudgesEnabled}
|
||||
<div class="pl-1 py-1 animate-fade-in">
|
||||
<Toggle
|
||||
bind:checked={settings.nudgesMuted}
|
||||
label="Mute for now"
|
||||
description="Stops delivery without forgetting your settings. Flip back off when you want the nudges back."
|
||||
/>
|
||||
<Toggle
|
||||
bind:checked={settings.nudgesSpeakAloud}
|
||||
label="Speak nudges aloud"
|
||||
description="Also reads the nudge out through your Read-aloud voice. Useful if you keep the sound off but look up sometimes."
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Read aloud (Phase 4 roadmap) -->
|
||||
<div class="border-b border-border-subtle">
|
||||
<button
|
||||
|
||||
@@ -100,6 +100,11 @@ function createFocusTimerStore() {
|
||||
}
|
||||
|
||||
function clear() {
|
||||
// Broadcast cancellation so downstream listeners (Phase 6 nudge bus)
|
||||
// can reset their inactivity-while-timer state. Only fires when a
|
||||
// timer was actually active — dismissing the completion flash
|
||||
// after natural completion doesn't re-fire the event.
|
||||
const wasActive = startedAt !== null && !completionFired;
|
||||
startedAt = null;
|
||||
durationMs = 0;
|
||||
taskId = null;
|
||||
@@ -108,6 +113,9 @@ function createFocusTimerStore() {
|
||||
completionFired = false;
|
||||
writePersisted(null);
|
||||
stopTick();
|
||||
if (wasActive && typeof window !== "undefined") {
|
||||
window.dispatchEvent(new CustomEvent("kon:focus-timer-cancelled"));
|
||||
}
|
||||
}
|
||||
|
||||
function start(seconds: number, opts?: { taskId?: string | null; label?: string | null }): void {
|
||||
|
||||
292
src/lib/stores/nudgeBus.svelte.ts
Normal file
292
src/lib/stores/nudgeBus.svelte.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
// Phase 6 Margot nudge bus. Frontend-owned, subscribes to in-app
|
||||
// signals Corbie already produces, applies suppression, and fans out
|
||||
// to OS notification (via Rust's deliver_nudge) + optional TTS.
|
||||
//
|
||||
// Why frontend-only: OS-wide keyboard/window detection is fragile
|
||||
// across Wayland/macOS/Windows (no sanctioned global-keyboard API on
|
||||
// Wayland, accessibility permission on macOS, message-loop hook on
|
||||
// Windows). The app-local signals we already have — timer state,
|
||||
// task completion, micro-step creation, app focus/visibility — cover
|
||||
// the Phase 6 trigger set without the platform pain.
|
||||
//
|
||||
// Suppression rules:
|
||||
// - nudgesEnabled && !nudgesMuted
|
||||
// - document has focus → never nudge (user is already looking)
|
||||
// - rolling 1-hour cap of 3 nudges
|
||||
// - timer-specific: no nudge in first 60 s of a running timer
|
||||
//
|
||||
// Trigger set v1 (in-app only):
|
||||
// - inactivity_with_active_timer
|
||||
// - pending_morning_triage
|
||||
// - micro_step_idle
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { hasTauriRuntime } from "$lib/utils/runtime.js";
|
||||
import { settings } from "$lib/stores/page.svelte.js";
|
||||
|
||||
const HOUR_MS = 60 * 60 * 1000;
|
||||
const INACTIVITY_TIMER_THRESHOLD_MS = 90_000;
|
||||
const TIMER_WARMUP_MS = 60_000;
|
||||
const MICRO_STEP_IDLE_MS = 15 * 60_000;
|
||||
const MORNING_TRIAGE_CHECK_INTERVAL_MS = 5 * 60_000;
|
||||
const MORNING_TRIAGE_TRIGGER_HOUR = 10;
|
||||
|
||||
interface TimerStartPayload {
|
||||
taskId?: string;
|
||||
seconds?: number;
|
||||
}
|
||||
|
||||
interface MicroStepPayload {
|
||||
parentTaskId: string;
|
||||
}
|
||||
|
||||
let started = false;
|
||||
let permissionRequested = false;
|
||||
let permissionGranted = false;
|
||||
|
||||
// Rolling timestamps of recent nudge deliveries (ms since epoch).
|
||||
// Older-than-one-hour entries are pruned lazily on each check.
|
||||
const recentNudges: number[] = [];
|
||||
|
||||
// Per-trigger state.
|
||||
let timerRunning = false;
|
||||
let timerStartedAt = 0;
|
||||
let timerNudgeFiredThisSession = false;
|
||||
let blurredAt: number | null = null;
|
||||
let blurCheckHandle: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
let triagePollHandle: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
// parentTaskId → scheduled timeout handle for "idle since step created".
|
||||
// Cleared when a step or parent task gets marked done in the same session.
|
||||
const microStepTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
// --- Permission ------------------------------------------------------
|
||||
|
||||
async function ensurePermission(): Promise<boolean> {
|
||||
if (!hasTauriRuntime()) return false;
|
||||
if (permissionRequested) return permissionGranted;
|
||||
permissionRequested = true;
|
||||
try {
|
||||
const plugin = await import("@tauri-apps/plugin-notification");
|
||||
const already = await plugin.isPermissionGranted();
|
||||
if (already) {
|
||||
permissionGranted = true;
|
||||
return true;
|
||||
}
|
||||
const result = await plugin.requestPermission();
|
||||
permissionGranted = result === "granted";
|
||||
return permissionGranted;
|
||||
} catch {
|
||||
permissionGranted = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Suppression -----------------------------------------------------
|
||||
|
||||
function pruneRecentNudges(now: number): void {
|
||||
while (recentNudges.length > 0 && now - recentNudges[0] > HOUR_MS) {
|
||||
recentNudges.shift();
|
||||
}
|
||||
}
|
||||
|
||||
function canNudgeNow(now: number): boolean {
|
||||
if (!settings.nudgesEnabled) return false;
|
||||
if (settings.nudgesMuted) return false;
|
||||
if (typeof document !== "undefined" && document.hasFocus()) return false;
|
||||
pruneRecentNudges(now);
|
||||
if (recentNudges.length >= 3) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Delivery --------------------------------------------------------
|
||||
|
||||
async function deliver(title: string, body: string): Promise<void> {
|
||||
const now = Date.now();
|
||||
if (!canNudgeNow(now)) return;
|
||||
const ok = await ensurePermission();
|
||||
if (!ok) return;
|
||||
recentNudges.push(now);
|
||||
try {
|
||||
await invoke("deliver_nudge", { input: { title, body } });
|
||||
} catch {
|
||||
// Nudges are fire-and-forget — surfacing an error to the user
|
||||
// would defeat the "anticipatory, not push-notification" framing.
|
||||
}
|
||||
if (settings.nudgesSpeakAloud) {
|
||||
try {
|
||||
await invoke("tts_speak", {
|
||||
text: body || title,
|
||||
rate: settings.ttsRate,
|
||||
voice: settings.ttsVoice ?? null,
|
||||
});
|
||||
} catch {
|
||||
// Same reasoning — never surface TTS failures through a nudge.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Trigger: inactivity with active timer ---------------------------
|
||||
|
||||
function onTimerStart(event: Event) {
|
||||
const detail = (event as CustomEvent<TimerStartPayload>).detail;
|
||||
timerRunning = true;
|
||||
timerStartedAt = Date.now();
|
||||
timerNudgeFiredThisSession = false;
|
||||
// If the user switched away before starting the timer, the next
|
||||
// blur check (below) will pick it up.
|
||||
void detail;
|
||||
}
|
||||
|
||||
function resetTimerState() {
|
||||
timerRunning = false;
|
||||
timerStartedAt = 0;
|
||||
timerNudgeFiredThisSession = false;
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
blurredAt = null;
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
blurredAt = Date.now();
|
||||
}
|
||||
|
||||
function checkInactivityWithActiveTimer(now: number) {
|
||||
if (!timerRunning || timerNudgeFiredThisSession) return;
|
||||
if (now - timerStartedAt < TIMER_WARMUP_MS) return;
|
||||
if (blurredAt === null) return;
|
||||
if (now - blurredAt < INACTIVITY_TIMER_THRESHOLD_MS) return;
|
||||
timerNudgeFiredThisSession = true;
|
||||
void deliver(
|
||||
"Still on that timer?",
|
||||
"It's been ticking while you've been away. Pick up where you left off, or stop it.",
|
||||
);
|
||||
}
|
||||
|
||||
// --- Trigger: pending morning triage ---------------------------------
|
||||
|
||||
function todayLocalKey(): string {
|
||||
const d = new Date();
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
let triageNudgeFiredOnKey: string | null = null;
|
||||
|
||||
async function checkPendingMorningTriage() {
|
||||
if (!settings.ritualsMorning) return;
|
||||
if (!hasTauriRuntime()) return;
|
||||
const now = new Date();
|
||||
if (now.getHours() < MORNING_TRIAGE_TRIGGER_HOUR) return;
|
||||
const today = todayLocalKey();
|
||||
if (triageNudgeFiredOnKey === today) return;
|
||||
let lastShown: string | null = null;
|
||||
try {
|
||||
lastShown = await invoke<string | null>("get_last_morning_triage");
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (lastShown === today) return;
|
||||
triageNudgeFiredOnKey = today;
|
||||
void deliver(
|
||||
"A few things waiting",
|
||||
"When you're ready, Corbie has your morning list. No rush.",
|
||||
);
|
||||
}
|
||||
|
||||
// --- Trigger: micro-step idle ----------------------------------------
|
||||
|
||||
function onMicroStepGenerated(event: Event) {
|
||||
const detail = (event as CustomEvent<MicroStepPayload>).detail;
|
||||
if (!detail?.parentTaskId) return;
|
||||
const parentTaskId = detail.parentTaskId;
|
||||
clearMicroStepTimer(parentTaskId);
|
||||
const handle = setTimeout(() => {
|
||||
microStepTimers.delete(parentTaskId);
|
||||
void deliver(
|
||||
"Still with that one?",
|
||||
"Your breakdown is here when you want to pick the first step.",
|
||||
);
|
||||
}, MICRO_STEP_IDLE_MS);
|
||||
microStepTimers.set(parentTaskId, handle);
|
||||
}
|
||||
|
||||
function clearMicroStepTimer(parentTaskId: string) {
|
||||
const handle = microStepTimers.get(parentTaskId);
|
||||
if (handle !== undefined) {
|
||||
clearTimeout(handle);
|
||||
microStepTimers.delete(parentTaskId);
|
||||
}
|
||||
}
|
||||
|
||||
function onStepOrTaskCompleted(event: Event) {
|
||||
const detail = (event as CustomEvent<{ id?: string; parentTaskId?: string }>).detail;
|
||||
// A completed subtask clears its parent's idle timer; a completed
|
||||
// parent task clears its own idle timer (the step decomposition
|
||||
// attached to that task is no longer waiting on anyone).
|
||||
if (detail?.parentTaskId) clearMicroStepTimer(detail.parentTaskId);
|
||||
if (detail?.id) clearMicroStepTimer(detail.id);
|
||||
}
|
||||
|
||||
// --- Lifecycle -------------------------------------------------------
|
||||
|
||||
export function startNudgeBus(): void {
|
||||
if (started) return;
|
||||
if (typeof window === "undefined") return;
|
||||
started = true;
|
||||
|
||||
window.addEventListener("kon:start-timer", onTimerStart);
|
||||
window.addEventListener("kon:focus-timer-complete", resetTimerState);
|
||||
window.addEventListener("kon:focus-timer-cancelled", resetTimerState);
|
||||
window.addEventListener("kon:microstep-generated", onMicroStepGenerated);
|
||||
window.addEventListener("kon:step-completed", onStepOrTaskCompleted);
|
||||
window.addEventListener("kon:task-completed", onStepOrTaskCompleted);
|
||||
window.addEventListener("focus", onFocus);
|
||||
window.addEventListener("blur", onBlur);
|
||||
|
||||
// Inactivity check runs while a timer is live. Low frequency is fine
|
||||
// — the nudge itself only fires once the 90 s threshold is crossed.
|
||||
blurCheckHandle = setInterval(() => {
|
||||
checkInactivityWithActiveTimer(Date.now());
|
||||
}, 10_000);
|
||||
|
||||
// Morning triage check runs on an interval + one immediate probe on
|
||||
// start. The Rust `get_last_morning_triage` call is cheap (one
|
||||
// SQLite read), so polling every 5 min costs nothing and means we
|
||||
// catch the 10:00 threshold without a scheduler.
|
||||
void checkPendingMorningTriage();
|
||||
triagePollHandle = setInterval(() => {
|
||||
void checkPendingMorningTriage();
|
||||
}, MORNING_TRIAGE_CHECK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
export function stopNudgeBus(): void {
|
||||
if (!started) return;
|
||||
started = false;
|
||||
|
||||
window.removeEventListener("kon:start-timer", onTimerStart);
|
||||
window.removeEventListener("kon:focus-timer-complete", resetTimerState);
|
||||
window.removeEventListener("kon:focus-timer-cancelled", resetTimerState);
|
||||
window.removeEventListener("kon:microstep-generated", onMicroStepGenerated);
|
||||
window.removeEventListener("kon:step-completed", onStepOrTaskCompleted);
|
||||
window.removeEventListener("kon:task-completed", onStepOrTaskCompleted);
|
||||
window.removeEventListener("focus", onFocus);
|
||||
window.removeEventListener("blur", onBlur);
|
||||
|
||||
if (blurCheckHandle !== null) {
|
||||
clearInterval(blurCheckHandle);
|
||||
blurCheckHandle = null;
|
||||
}
|
||||
if (triagePollHandle !== null) {
|
||||
clearInterval(triagePollHandle);
|
||||
triagePollHandle = null;
|
||||
}
|
||||
for (const handle of microStepTimers.values()) clearTimeout(handle);
|
||||
microStepTimers.clear();
|
||||
|
||||
resetTimerState();
|
||||
blurredAt = null;
|
||||
triageNudgeFiredOnKey = null;
|
||||
permissionRequested = false;
|
||||
}
|
||||
@@ -79,6 +79,9 @@ const defaults: SettingsState = {
|
||||
ritualsEvening: false,
|
||||
launchAtLogin: false,
|
||||
ritualsPromptSeen: false,
|
||||
nudgesEnabled: false,
|
||||
nudgesMuted: false,
|
||||
nudgesSpeakAloud: false,
|
||||
};
|
||||
|
||||
function canUseStorage(): boolean {
|
||||
@@ -453,6 +456,12 @@ export async function completeTask(id: string) {
|
||||
try {
|
||||
await invoke("complete_task_cmd", { id });
|
||||
applyLocalTaskUpdate(id, { done: true, doneAt: new Date().toISOString() });
|
||||
// Phase 6 nudge-bus signal. Fire-and-forget; the bus subscribes
|
||||
// to this to clear micro-step-idle timers for the completed task
|
||||
// and, later, to drive Phase 7 "after a task completes" rules.
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(new CustomEvent("kon:task-completed", { detail: { id } }));
|
||||
}
|
||||
} catch (err) {
|
||||
toasts.error("Couldn't complete task", errorMessage(err));
|
||||
}
|
||||
|
||||
@@ -105,6 +105,18 @@ export interface SettingsState {
|
||||
* prompt from re-appearing on every update-triggered reboot.
|
||||
*/
|
||||
ritualsPromptSeen: boolean;
|
||||
/**
|
||||
* Phase 6 Margot nudges. All off by default per Jake's standing rule
|
||||
* about opt-in nudging. `nudgesMuted` is separate from `nudgesEnabled`
|
||||
* so the user can flip a hard mute without losing their preferences.
|
||||
*/
|
||||
nudgesEnabled: boolean;
|
||||
nudgesMuted: boolean;
|
||||
/**
|
||||
* When true, nudges also speak their body through Phase 4 TTS. Only
|
||||
* has effect when `nudgesEnabled` is true.
|
||||
*/
|
||||
nudgesSpeakAloud: boolean;
|
||||
}
|
||||
|
||||
export interface Profile {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import { toasts } from "$lib/stores/toasts.svelte.js";
|
||||
import { initI18n } from "$lib/i18n";
|
||||
import { refreshLlmStatus } from "$lib/stores/llmStatus.svelte.js";
|
||||
import { startNudgeBus, stopNudgeBus } from "$lib/stores/nudgeBus.svelte.ts";
|
||||
|
||||
import { page as sveltePage } from "$app/stores";
|
||||
|
||||
@@ -277,6 +278,12 @@
|
||||
// Phase 5: subscribe to tray wind-down event.
|
||||
setupWindDownListener();
|
||||
|
||||
// Phase 6: start the nudge bus. The bus is self-gated on
|
||||
// settings.nudgesEnabled internally — starting it here is cheap
|
||||
// (just event listeners + two intervals), and means users don't
|
||||
// need to restart after flipping the toggle.
|
||||
startNudgeBus();
|
||||
|
||||
// Diagnostics: capture every uncaught frontend error to error_log.
|
||||
installGlobalErrorCapture();
|
||||
|
||||
@@ -384,6 +391,7 @@
|
||||
if (unlistenWindDown) {
|
||||
unlistenWindDown();
|
||||
}
|
||||
stopNudgeBus();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user