dsh-deeppilot 0.6.0 → 0.6.2

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/COMPATIBILITY.md CHANGED
@@ -7,7 +7,7 @@ This file separates tested evidence from intended behavior. Passing unit tests d
7
7
  | Component | Baseline | Evidence |
8
8
  |---|---|---|
9
9
  | Node.js | 22 or newer | package engine and CI |
10
- | DSH CLI and Host API | `0.1.2-rc.1` minimum for plugin `0.6.0-alpha.4` | rc.1 package-family typecheck/build; bridge and `/phone` protocol tests |
10
+ | DSH CLI and Host API | `0.1.5-rc.1` minimum for plugin `0.6.x` | rc.1 package-family typecheck/build; bridge and `/phone` protocol tests |
11
11
  | Host OS | macOS, Linux, Windows on packaged amd64/arm64 helper targets | helper checksums plus user-confirmed Windows/Linux Funnel launch and connection; local LAN validation remains part of alpha testing |
12
12
  | Remote access | Tailscale Funnel, ports 443/8443/10000 | helper and supervisor tests |
13
13
  | iOS | native DeepPilot client, protocol v2 | simulator build and v2 pairing/challenge evidence |
@@ -18,8 +18,8 @@ This file separates tested evidence from intended behavior. Passing unit tests d
18
18
  - Bearer authentication, URL credentials, and first-frame shared tokens are rejected. A supported client registers a P-256 public key through `/phone/pair` and signs each WebSocket challenge.
19
19
  - Without a compatible embedded helper, the core bridge and trusted-LAN mode can still run; remote Funnel reports `unavailable`.
20
20
  - If a DSH Host API is missing, only the dependent capability should be disabled. The plugin must not crash the Host.
21
- - Plugin `0.6.0-alpha.4` requires DSH `0.1.2-rc.1` or newer. Older plugin
22
- alphas used developer-preview DSH builds and remain historical artifacts.
21
+ - Plugin `0.6.x` requires DSH `0.1.5-rc.1` or newer. Older plugin
22
+ versions used developer-preview DSH builds and remain historical artifacts.
23
23
  - The compatibility facade converts the
24
24
  current Session controller's raw event arrays into the stable wrapped
25
25
  history entries consumed by the phone bridge. Sessions persisted by earlier
@@ -31,7 +31,7 @@ This file separates tested evidence from intended behavior. Passing unit tests d
31
31
 
32
32
  - Intel macOS support for the embedded helper;
33
33
  - signed/notarized helper distribution;
34
- - repair or migration of persisted history rejected by the `0.1.2-rc.1`
34
+ - repair or migration of persisted history rejected by the `0.1.5-rc.1`
35
35
  Host's own session reader;
36
36
  - every DSH developer-preview revision;
37
37
  - physical-device performance and every carrier/network combination;
package/PRIVACY.md CHANGED
@@ -7,7 +7,7 @@ DeepPilot connects directly to the DSH Host controlled by the user. The plugin d
7
7
  By default, runtime state is stored under `$DSH_HOME/deeppilot/`:
8
8
 
9
9
  - `host-id`: a random, non-secret stable audience identifier;
10
- - `devices-v2.json`: paired public keys, fingerprints, scopes, device metadata, revocation/last-seen timestamps, notification preferences, and APNs registrations;
10
+ - `devices-v2.json`: paired public keys, fingerprints, scopes, device metadata, revocation/last-seen timestamps, notification preferences, APNs alert registrations, WidgetKit push registrations (a separate token per device used only for content-free widget refresh invalidations), and at most one Live Activity registration per device (activity identifier, session identifier, APNs Live Activity update token, environment, and expiry);
11
11
  - `tailscale/`: local state for the optional embedded tsnet node.
12
12
 
13
13
  The directory is owner-only mode `0700`. The Mac never receives or stores iPhone private signing keys. Pairing codes exist only in plugin memory for up to five minutes and are not written to disk.
@@ -31,6 +31,28 @@ Direct APNs mode sends the device token and notification payload to Apple using
31
31
 
32
32
  The relay does not receive complete history, attachments, live token streams, unrelated prompts, pairing codes, device keys, the device registry, or Tailscale state. Disabling offline push removes this path.
33
33
 
34
+ ## WidgetKit refresh push
35
+
36
+ The iOS home-screen widget may register a separate WidgetKit push token (iOS 26+). These invalidations are content-free: the APNs payload is fixed to `{ "aps": { "content-changed": true } }` with no session, title, or body fields, so neither Apple nor the relay learns anything about Host activity beyond "something changed, please refetch". The widget then pulls data over its own short-lived, authenticated read-only connection directly to the user's DSH Host. Widget tokens are stored alongside the alert token in `devices-v2.json`, are throttled to at most one invalidation per Host per 30 seconds, expire after 7 days without re-registration, and are deleted when the device is revoked.
37
+
38
+ ## Live Activity push
39
+
40
+ A Live Activity on the Lock Screen or in the Dynamic Island may register a separate ActivityKit **update** token. Unlike the WidgetKit path, Live Activity updates are **not content-free**: each push carries a bounded progress projection — the session title (up to 100 Unicode code points), the current in-progress or pending todo item's text (up to 160 code points), the todo done/total counts, and a fixed phase value (`running`, `approval`, `question`, `ended`, or `unavailable`). This projection goes to Apple in direct APNs mode and to the relay in relay mode. It never contains conversation history, message bodies, prompt text, attachments, full todo lists, file paths, or model output.
41
+
42
+ The activity must be started by the user on the iPhone; there is no push-to-start, and the activity opens no network connection of its own. Only one Live Activity registration is kept per device, registrations expire after eight hours, and the registration is deleted on unregister, token rotation, or device revocation. Relay mode carries only this bounded projection and rejects unknown content-state fields, oversized text, invalid counts, stale timestamps, and event/phase mismatches; the updated relay must be deployed before background Live Activity updates work through relay mode. System delivery and refresh budgets still apply, so updates may be coalesced, delayed, or dropped by iOS.
43
+
44
+ `push.contentMode: generic` applies to alert notifications only. Live Activity content states always carry the bounded title and current-task text described above, because the activity is rendered from this push; do not register a Live Activity on a device whose Lock Screen should never show session text.
45
+
34
46
  ## Logs
35
47
 
36
48
  Routine logs contain lifecycle state, delivery outcomes, and process-local salted hashes for source/device identifiers. Logs are designed not to contain pairing codes, key material, APNs tokens, or message bodies. Frame diagnostics appear only with `debug`.
49
+
50
+ ### Generic offline notifications
51
+
52
+ Set `push.contentMode` to `generic` in the plugin configuration to replace conversation-derived notification titles and bodies with generic status text **before** either APNs or the Relay receives them. The default is `preview`, preserving short previews. Device tokens, Host audience, session and notification identifiers, and categories are still sent. This option controls offline pushes; local notifications generated by the iOS app from its direct connection are unchanged.
53
+
54
+ For identity-tracked sends, the Host stores a bounded local delivery journal containing
55
+ hashed device/send keys, content fingerprints, session identifiers, timestamps, and
56
+ receipts. It does not store prompt bodies or image data in that journal. The iOS app
57
+ stores unresolved send IDs, display text and attachment counts in its instance-scoped
58
+ SQLite cache; removing that instance removes this outbox with its database.
package/README.md CHANGED
@@ -37,21 +37,21 @@ does not require the helper.
37
37
 
38
38
  | Plugin version | Required DSH | How to install |
39
39
  |---|---|---|
40
- | `0.6.0-alpha.4` (new, `alpha` tag) | DSH `0.1.2-rc.1` or newer | `dsh plugin --profile web add dsh-deeppilot@alpha` |
41
- | `0.5.x` (previous stable, `latest`) | DSH `0.1.1-rc.2`–`0.1.2-alpha.1` | `dsh plugin --profile web add dsh-deeppilot` |
40
+ | `0.6.x` (current stable, `latest`) | DSH `0.1.5-rc.1` or newer | `dsh plugin --profile web add dsh-deeppilot` |
41
+ | `0.5.x` (previous stable) | DSH `0.1.1-rc.2`–`0.1.2-alpha.1` | `dsh plugin --profile web add dsh-deeppilot@0.5.0` |
42
42
 
43
- `0.6.0-alpha.4` is built and typechecked against the DSH
44
- [0.1.2-rc.1](https://www.npmjs.com/package/@deepseek-ai/dsh/v/0.1.2-rc.1)
43
+ `0.6.x` is built and typechecked against the DSH
44
+ [0.1.5-rc.1](https://www.npmjs.com/package/@deepseek-ai/dsh/v/0.1.5-rc.1)
45
45
  Host and client package family. It uses the Gateway multi-client Remote Events
46
46
  routing that keeps Web and DeepPilot independently answerable. Earlier plugin
47
- alphas remain historical artifacts; users installing the current `alpha` tag
48
- should update DSH to `0.1.2-rc.1` or newer.
47
+ alphas remain historical artifacts; users installing the `alpha` tag for
48
+ pre-release testing should update DSH to `0.1.5-rc.1` or newer.
49
49
 
50
50
  ```sh
51
- # DSH 0.1.2-rc.1 or newer (recommended):
52
- dsh plugin --profile web add dsh-deeppilot@alpha
53
- # DSH 0.1.1-rc.2 through 0.1.2-alpha.1 (previous stable):
51
+ # DSH 0.1.5-rc.1 or newer (recommended):
54
52
  dsh plugin --profile web add dsh-deeppilot
53
+ # DSH 0.1.1-rc.2 through 0.1.2-alpha.1 (previous stable):
54
+ dsh plugin --profile web add dsh-deeppilot@0.5.0
55
55
  dsh web
56
56
  ```
57
57
 
@@ -78,33 +78,33 @@ DeepPilot state under `$DSH_HOME/deeppilot/`.
78
78
 
79
79
  ## Publishing (maintainers)
80
80
 
81
- `0.6.0-alpha.4` targets DSH `0.1.2-rc.1`+; `0.5.x` stays compatible with
81
+ `0.6.x` targets DSH `0.1.5-rc.1`+; `0.5.x` stays compatible with
82
82
  DSH `0.1.1-rc.2`–`0.1.2-alpha.1`. Keep both published:
83
83
 
84
84
  1. Bump `version` in `package.json` and in the root `""` entry of
85
85
  `package-lock.json`, then run `npm test && npm run typecheck && npm run build`
86
86
  and inspect `npm pack --dry-run --json` (the check
87
- `tests/compatibility-metadata.test.ts` enforces the `^0.1.2-rc.1` peer
87
+ `tests/compatibility-metadata.test.ts` enforces the `^0.1.5-rc.1` peer
88
88
  ranges).
89
89
  2. Commit the release and push it. `npm publish` runs `prepack` (build) and
90
90
  `prepublishOnly` (test + typecheck) automatically.
91
- 3. Publish the alpha line without touching `latest`:
91
+ 3. Publish pre-releases without touching `latest`:
92
92
 
93
93
  ```sh
94
94
  npm publish --tag alpha
95
95
  ```
96
96
 
97
97
  After a successful publish, `npm view dsh-deeppilot dist-tags --json` shows
98
- `"latest": "0.5.x"` and `"alpha": "0.6.0-alpha.x"`. Verify the published
99
- package by installing it into a DSH `0.1.2-rc.1` profile before pointing
98
+ `"latest": "0.6.x"` and `"alpha": "0.6.x-alpha.y"`. Verify the published
99
+ package by installing it into a DSH `0.1.5-rc.1` profile before pointing
100
100
  users at it.
101
- 4. Tag the release commit `v0.6.0-alpha.x` and prepare a GitHub Release
101
+ 4. Tag the release commit `vX.Y.Z` and prepare a GitHub Release
102
102
  (English + 简体中文 notes) that links this README section.
103
- 5. When the alpha graduates to stable, bump to `0.6.0` and publish with
104
- `npm publish --tag latest`, which moves `latest` to the new line. Stable
105
- releases must never be published with `--tag alpha`.
103
+ 5. Publish stable releases with `npm publish --tag latest`, which moves
104
+ `latest` to the new version. Stable releases must never be published
105
+ with `--tag alpha`.
106
106
 
107
- Never run `npm publish` from a copy that still has the old `0.5.x` version.
107
+ Never run `npm publish` from a copy that still has an older version.
108
108
 
109
109
 
110
110
  ## Connection and privacy
package/README.zh-CN.md CHANGED
@@ -34,20 +34,20 @@
34
34
 
35
35
  | 插件版本 | 所需 DSH | 安装命令 |
36
36
  |---|---|---|
37
- | `0.6.0-alpha.4`(新版,`alpha` tag) | DSH `0.1.2-rc.1` 或更高 | `dsh plugin --profile web add dsh-deeppilot@alpha` |
38
- | `0.5.x`(旧版稳定版,`latest`) | DSH `0.1.1-rc.2`–`0.1.2-alpha.1` | `dsh plugin --profile web add dsh-deeppilot` |
37
+ | `0.6.x`(当前稳定版,`latest`) | DSH `0.1.5-rc.1` 或更高 | `dsh plugin --profile web add dsh-deeppilot` |
38
+ | `0.5.x`(旧版稳定版) | DSH `0.1.1-rc.2`–`0.1.2-alpha.1` | `dsh plugin --profile web add dsh-deeppilot@0.5.0` |
39
39
 
40
- `0.6.0-alpha.4` 基于 DSH
41
- [0.1.2-rc.1](https://www.npmjs.com/package/@deepseek-ai/dsh/v/0.1.2-rc.1)
40
+ `0.6.x` 基于 DSH
41
+ [0.1.5-rc.1](https://www.npmjs.com/package/@deepseek-ai/dsh/v/0.1.5-rc.1)
42
42
  的 Host 与 client 包族构建并完成类型检查。它使用 Gateway 多 Client Remote Events
43
43
  路由,使 Web 与 DeepPilot 能独立处理同一交互。旧 alpha 仅保留为历史产物;安装
44
- 当前 `alpha` tag 的用户应先把 DSH 更新到 `0.1.2-rc.1` 或更高版本。
44
+ `alpha` tag 进行预发布测试的用户应先把 DSH 更新到 `0.1.5-rc.1` 或更高版本。
45
45
 
46
46
  ```sh
47
- # DSH 0.1.2-rc.1 或更高(推荐):
48
- dsh plugin --profile web add dsh-deeppilot@alpha
49
- # DSH 0.1.1-rc.2 至 0.1.2-alpha.1(旧版稳定):
47
+ # DSH 0.1.5-rc.1 或更高(推荐):
50
48
  dsh plugin --profile web add dsh-deeppilot
49
+ # DSH 0.1.1-rc.2 至 0.1.2-alpha.1(旧版稳定):
50
+ dsh plugin --profile web add dsh-deeppilot@0.5.0
51
51
  dsh web
52
52
  ```
53
53
 
@@ -71,30 +71,30 @@ dsh plugin --profile web remove dsh-deeppilot
71
71
 
72
72
  ## 发布说明(维护者)
73
73
 
74
- `0.6.0-alpha.4` 面向 DSH `0.1.2-rc.1`+;`0.5.x` 保持兼容 DSH
74
+ `0.6.x` 面向 DSH `0.1.5-rc.1`+;`0.5.x` 保持兼容 DSH
75
75
  `0.1.1-rc.2`–`0.1.2-alpha.1`,两个版本线都要保持发布:
76
76
 
77
77
  1. 同步修改 `package.json` 与 `package-lock.json` 根 `""` 条目中的
78
78
  `version`,然后运行 `npm test && npm run typecheck && npm run build`,
79
79
  并检查 `npm pack --dry-run --json`(`tests/compatibility-metadata.test.ts`
80
- 会强制校验 `^0.1.2-rc.1` 的 peer 范围)。
80
+ 会强制校验 `^0.1.5-rc.1` 的 peer 范围)。
81
81
  2. 提交发布并推送。`npm publish` 会自动执行 `prepack`(构建)与
82
82
  `prepublishOnly`(测试 + 类型检查)。
83
- 3. 发布 alpha 版本线,不要动 `latest`:
83
+ 3. 发布预发布版本,不要动 `latest`:
84
84
 
85
85
  ```sh
86
86
  npm publish --tag alpha
87
87
  ```
88
88
 
89
89
  发布成功后,`npm view dsh-deeppilot dist-tags --json` 应显示
90
- `"latest": "0.5.x"` 与 `"alpha": "0.6.0-alpha.x"`。向用户推荐前,请先在
91
- DSH `0.1.2-rc.1` profile 中安装验证发布的包。
92
- 4. 为发布提交打 `v0.6.0-alpha.x` tag,并准备包含英文与简体中文说明的
90
+ `"latest": "0.6.x"` 与 `"alpha": "0.6.x-alpha.y"`。向用户推荐前,请先在
91
+ DSH `0.1.5-rc.1` profile 中安装验证发布的包。
92
+ 4. 为发布提交打 `vX.Y.Z` tag,并准备包含英文与简体中文说明的
93
93
  GitHub Release,链接本 README 的发布说明。
94
- 5. alpha 转正时升级到 `0.6.0`,用 `npm publish --tag latest` 发布,使
95
- `latest` 切换到新版线。稳定版绝不使用 `--tag alpha` 发布。
94
+ 5. 稳定版用 `npm publish --tag latest` 发布,使 `latest` 切换到新版本。
95
+ 稳定版绝不使用 `--tag alpha` 发布。
96
96
 
97
- 绝不要从仍是旧 `0.5.x` 版本的副本执行 `npm publish`。
97
+ 绝不要从仍是旧版本的副本执行 `npm publish`。
98
98
 
99
99
 
100
100
  ## 连接与隐私
package/lib/client.js CHANGED
@@ -539,6 +539,8 @@ window.__ModuleLoader__.load({
539
539
  "devices.col.fingerprint": "密钥指纹",
540
540
  "devices.col.actions": "操作",
541
541
  "devices.revoke": "删除",
542
+ "devices.revokeAria": "删除设备“{name}”",
543
+ "devices.deleting": "删除中…",
542
544
  "devices.revokeConfirm": "确定删除设备“{name}”吗?该设备会立即断开,再次使用时需要重新配对。",
543
545
  "devices.revoked": "设备已删除",
544
546
  "devices.revokeFailed": "设备删除失败:",
@@ -719,6 +721,8 @@ window.__ModuleLoader__.load({
719
721
  "devices.col.fingerprint": "Key fingerprint",
720
722
  "devices.col.actions": "Actions",
721
723
  "devices.revoke": "Delete",
724
+ "devices.revokeAria": "Delete device “{name}”",
725
+ "devices.deleting": "Deleting…",
722
726
  "devices.revokeConfirm": "Delete “{name}”? It will disconnect immediately and must pair again before reconnecting.",
723
727
  "devices.revoked": "Device deleted",
724
728
  "devices.revokeFailed": "Failed to delete device: ",
@@ -881,10 +885,15 @@ window.__ModuleLoader__.load({
881
885
  ".pbb-action:disabled{cursor:default;opacity:.5}",
882
886
  ".pbb-actionSelected{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-state-success-primary,#22a06b);background:var(--dsw-alias-bg-module-platform)}",
883
887
  ".pbb-targetList{display:flex;flex-wrap:wrap;gap:6px;padding-top:4px}",
884
- ".pbb-actionDanger{color:#fff;background:#dc2626;border-color:#dc2626;line-height:1.35;padding:4px 10px;box-shadow:0 1px 2px rgba(127,29,29,.18);transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease,transform .1s ease}",
885
- ".pbb-actionDanger:hover:not(:disabled){color:#fff;background:#b91c1c;border-color:#b91c1c;box-shadow:0 2px 5px rgba(127,29,29,.24)}",
886
- ".pbb-actionDanger:active:not(:disabled){background:#991b1b;border-color:#991b1b;box-shadow:0 1px 2px rgba(127,29,29,.2);transform:translateY(1px)}",
887
- ".pbb-actionDanger:focus-visible{outline:2px solid rgba(220,38,38,.32);outline-offset:2px}",
888
+ ".pbb-actionDangerGhost{color:#dc2626;border-color:rgba(220,38,38,.38);background:transparent;line-height:1.35;padding:3px 9px;box-shadow:none;transition:color .15s ease,background-color .15s ease,border-color .15s ease,box-shadow .15s ease,transform .1s ease}",
889
+ ".pbb-actionDangerGhost:hover:not(:disabled){color:#b91c1c;background:rgba(220,38,38,.09);border-color:rgba(185,28,28,.5);box-shadow:0 1px 2px rgba(127,29,29,.1)}",
890
+ ".pbb-actionDangerGhost:active:not(:disabled){color:#991b1b;background:rgba(220,38,38,.14);border-color:rgba(153,27,27,.55);box-shadow:none;transform:translateY(1px)}",
891
+ ".pbb-actionDangerGhost:focus-visible{outline:2px solid rgba(220,38,38,.32);outline-offset:1px}",
892
+ ".pbb-actionDangerGhost:disabled{cursor:default;opacity:.62}",
893
+ ".pbb-actionIcon{flex:none;display:inline-block;vertical-align:-2px;margin-right:5px;pointer-events:none}",
894
+ ".pbb-actionBusy{display:inline-flex;align-items:center;gap:6px}",
895
+ ".pbb-actionBusy::before{content:\"\";width:9px;height:9px;border-radius:50%;border:1.5px solid rgba(220,38,38,.35);border-top-color:currentColor;animation:pbb-spin .7s linear infinite}",
896
+ "@keyframes pbb-spin{to{transform:rotate(360deg)}}",
888
897
  ".pbb-qrPanel{display:flex;flex-direction:column;align-items:center;gap:9px;padding:12px 0 4px}",
889
898
  ".pbb-qrImage{width:240px;height:240px;max-width:100%;background:#fff;border-radius:10px;padding:8px;box-sizing:border-box}",
890
899
  ".pbb-pairCodeBlock{width:min(100%,420px);display:flex;flex-direction:column;gap:5px}",
@@ -3411,6 +3420,21 @@ window.__ModuleLoader__.load({
3411
3420
  }
3412
3421
  //#endregion
3413
3422
  //#region src/client/settings-page.ts
3423
+ /** Inline trash icon for the compact destructive row action. Kept as a tiny
3424
+ * element (no icon dependency) so the client bundle stays dependency-free
3425
+ * apart from React. */
3426
+ const TrashIcon = () => (0, react.createElement)("svg", {
3427
+ className: "pbb-actionIcon",
3428
+ viewBox: "0 0 16 16",
3429
+ width: 13,
3430
+ height: 13,
3431
+ fill: "none",
3432
+ stroke: "currentColor",
3433
+ strokeWidth: 1.5,
3434
+ strokeLinecap: "round",
3435
+ strokeLinejoin: "round",
3436
+ "aria-hidden": true
3437
+ }, (0, react.createElement)("path", { d: "M2.5 4.5h11" }), (0, react.createElement)("path", { d: "M6.5 2.5h3" }), (0, react.createElement)("path", { d: "M4 4.5l.6 8.2a1 1 0 0 0 1 .9h4.8a1 1 0 0 0 1-.9l.6-8.2" }), (0, react.createElement)("path", { d: "M6.6 7v4.2" }), (0, react.createElement)("path", { d: "M9.4 7v4.2" }));
3414
3438
  async function writeClipboard(t, value) {
3415
3439
  try {
3416
3440
  await navigator.clipboard.writeText(value);
@@ -3754,10 +3778,11 @@ window.__ModuleLoader__.load({
3754
3778
  const visibleDevices = report?.devices.filter((device) => device.revokedAt === void 0) ?? [];
3755
3779
  const deviceTable = visibleDevices.length > 0 ? (0, react.createElement)("table", { className: "pbb-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, (0, react.createElement)("th", null, translateWith(props.t, "devices.col.name")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.fingerprint")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.lastSeen")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.actions")))), (0, react.createElement)("tbody", null, visibleDevices.map((d) => (0, react.createElement)("tr", { key: d.deviceId }, (0, react.createElement)("td", null, d.deviceName, (0, react.createElement)("div", { className: "pbb-diag" }, d.appVersion)), (0, react.createElement)("td", null, (0, react.createElement)("code", { className: "pbb-token" }, d.fingerprint.slice(0, 12))), (0, react.createElement)("td", null, new Date(d.lastSeenTs).toLocaleString()), (0, react.createElement)("td", null, (0, react.createElement)("button", {
3756
3780
  type: "button",
3757
- className: "pbb-action pbb-actionDanger",
3781
+ className: "pbb-action pbb-actionDangerGhost",
3758
3782
  disabled: deviceBusy === d.deviceId,
3783
+ "aria-label": deviceBusy === d.deviceId ? translateWith(props.t, "devices.deleting", { name: d.deviceName }) : translateWith(props.t, "devices.revokeAria", { name: d.deviceName }),
3759
3784
  onClick: () => revokeDevice(d.deviceId, d.deviceName)
3760
- }, translateWith(props.t, "devices.revoke"))))))) : (0, react.createElement)("p", { className: "pbb-empty" }, translateWith(props.t, "devices.empty"));
3785
+ }, deviceBusy === d.deviceId ? null : TrashIcon(), deviceBusy === d.deviceId ? (0, react.createElement)("span", { className: "pbb-actionBusy" }, translateWith(props.t, "devices.deleting")) : translateWith(props.t, "devices.revoke"))))))) : (0, react.createElement)("p", { className: "pbb-empty" }, translateWith(props.t, "devices.empty"));
3761
3786
  const switchTitle = translateWith(props.t, "master.title");
3762
3787
  const switchDesc = switchReady ? enabled ? translateWith(props.t, "master.on") : translateWith(props.t, "master.off") : translateWith(props.t, "master.loading");
3763
3788
  return (0, react.createElement)("div", { className: "pbb-section" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("h2", { className: "pbb-title" }, "DeepPilot"), (0, react.createElement)("div", { style: { flex: "1" } }), (0, react.createElement)("button", {