@peers-app/peers-ui 0.27.3 → 0.27.5

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.
Files changed (311) hide show
  1. package/dist/screens/network-viewer/group-details-modal.d.ts +4 -0
  2. package/dist/screens/network-viewer/group-details-modal.js +6 -46
  3. package/dist/screens/network-viewer/network-viewer-ipc.d.ts +3 -2
  4. package/dist/screens/network-viewer/network-viewer-ipc.js +6 -0
  5. package/dist/screens/network-viewer/sync-recovery.js +103 -1
  6. package/dist/screens/variables/variable-details.js +2 -1
  7. package/dist/tabs-layout/network-status-indicator.js +26 -12
  8. package/dist/tabs-layout/use-network-status.d.ts +6 -1
  9. package/dist/tabs-layout/use-network-status.js +27 -4
  10. package/package.json +9 -3
  11. package/.claude/settings.local.json +0 -34
  12. package/.github/README.md +0 -52
  13. package/.github/workflows/delete-all-artifacts.yml +0 -49
  14. package/.github/workflows/publish.yml +0 -51
  15. package/babel.config.js +0 -7
  16. package/biome.json +0 -198
  17. package/dist/app-updates/app-updates-consumer.test.d.ts +0 -1
  18. package/dist/app-updates/app-updates-consumer.test.js +0 -56
  19. package/dist/app-updates/app-updates-settings.test.d.ts +0 -1
  20. package/dist/app-updates/app-updates-settings.test.js +0 -93
  21. package/dist/app-updates/remote-app-update-panel.test.d.ts +0 -1
  22. package/dist/app-updates/remote-app-update-panel.test.js +0 -165
  23. package/dist/components/access-level-field.test.d.ts +0 -1
  24. package/dist/components/access-level-field.test.js +0 -28
  25. package/dist/components/message-logs/tool-access-approval-card.test.d.ts +0 -1
  26. package/dist/components/message-logs/tool-access-approval-card.test.js +0 -102
  27. package/dist/components/responsive-section-nav.test.d.ts +0 -1
  28. package/dist/components/responsive-section-nav.test.js +0 -45
  29. package/dist/globals-hash.test.d.ts +0 -1
  30. package/dist/globals-hash.test.js +0 -16
  31. package/dist/hooks/use-observable-state.test.d.ts +0 -1
  32. package/dist/hooks/use-observable-state.test.js +0 -64
  33. package/dist/hooks/use-observable.test.d.ts +0 -1
  34. package/dist/hooks/use-observable.test.js +0 -191
  35. package/dist/hooks/use-rows.test.d.ts +0 -1
  36. package/dist/hooks/use-rows.test.js +0 -175
  37. package/dist/hooks/use-tool.test.d.ts +0 -1
  38. package/dist/hooks/use-tool.test.js +0 -190
  39. package/dist/host-shell/host-shell-consumer.test.d.ts +0 -1
  40. package/dist/host-shell/host-shell-consumer.test.js +0 -28
  41. package/dist/root-layout/root-layout-host.test.d.ts +0 -1
  42. package/dist/root-layout/root-layout-host.test.js +0 -86
  43. package/dist/screens/account/account-screen.test.d.ts +0 -1
  44. package/dist/screens/account/account-screen.test.js +0 -51
  45. package/dist/screens/assistants/assistant-config.test.d.ts +0 -1
  46. package/dist/screens/assistants/assistant-config.test.js +0 -57
  47. package/dist/screens/contacts/contact-list.test.d.ts +0 -1
  48. package/dist/screens/contacts/contact-list.test.js +0 -48
  49. package/dist/screens/device-pairing/device-pairing.test.d.ts +0 -1
  50. package/dist/screens/device-pairing/device-pairing.test.js +0 -119
  51. package/dist/screens/devices/devices-list.test.d.ts +0 -1
  52. package/dist/screens/devices/devices-list.test.js +0 -53
  53. package/dist/screens/groups/group-list.test.d.ts +0 -1
  54. package/dist/screens/groups/group-list.test.js +0 -38
  55. package/dist/screens/identity/identity-routes.test.d.ts +0 -1
  56. package/dist/screens/identity/identity-routes.test.js +0 -51
  57. package/dist/screens/identity/identity-screen.test.d.ts +0 -1
  58. package/dist/screens/identity/identity-screen.test.js +0 -103
  59. package/dist/screens/invites/accept-invite.test.d.ts +0 -1
  60. package/dist/screens/invites/accept-invite.test.js +0 -103
  61. package/dist/screens/invites/activity-view.test.d.ts +0 -1
  62. package/dist/screens/invites/activity-view.test.js +0 -108
  63. package/dist/screens/invites/invite-classify.test.d.ts +0 -1
  64. package/dist/screens/invites/invite-classify.test.js +0 -115
  65. package/dist/screens/network-viewer/network-viewer-ipc.test.d.ts +0 -1
  66. package/dist/screens/network-viewer/network-viewer-ipc.test.js +0 -61
  67. package/dist/screens/network-viewer/network-viewer.section.test.d.ts +0 -1
  68. package/dist/screens/network-viewer/network-viewer.section.test.js +0 -63
  69. package/dist/screens/network-viewer/sync-recovery.test.d.ts +0 -1
  70. package/dist/screens/network-viewer/sync-recovery.test.js +0 -104
  71. package/dist/screens/packages/package-helpers.test.d.ts +0 -1
  72. package/dist/screens/packages/package-helpers.test.js +0 -53
  73. package/dist/screens/packages/package-runtime-status.test.d.ts +0 -1
  74. package/dist/screens/packages/package-runtime-status.test.js +0 -50
  75. package/dist/screens/packages/packages-ipc.test.d.ts +0 -1
  76. package/dist/screens/packages/packages-ipc.test.js +0 -43
  77. package/dist/screens/settings/settings-page.test.d.ts +0 -1
  78. package/dist/screens/settings/settings-page.test.js +0 -19
  79. package/dist/screens/tools/tool-access-ipc.test.d.ts +0 -1
  80. package/dist/screens/tools/tool-access-ipc.test.js +0 -40
  81. package/dist/screens/tools/tool-catalog-ipc.test.d.ts +0 -1
  82. package/dist/screens/tools/tool-catalog-ipc.test.js +0 -52
  83. package/dist/tabs-layout/network-status-indicator.test.d.ts +0 -1
  84. package/dist/tabs-layout/network-status-indicator.test.js +0 -665
  85. package/dist/tabs-layout/tabs-state.test.d.ts +0 -1
  86. package/dist/tabs-layout/tabs-state.test.js +0 -110
  87. package/dist/tabs-layout/use-network-status.test.d.ts +0 -1
  88. package/dist/tabs-layout/use-network-status.test.js +0 -45
  89. package/dist/ui-router/ui-loader.test.d.ts +0 -1
  90. package/dist/ui-router/ui-loader.test.js +0 -91
  91. package/jest.config.js +0 -25
  92. package/release-guard.js +0 -10
  93. package/src/app-updates/app-update-controls.tsx +0 -73
  94. package/src/app-updates/app-updates-consumer.test.ts +0 -83
  95. package/src/app-updates/app-updates-consumer.ts +0 -121
  96. package/src/app-updates/app-updates-settings.test.tsx +0 -109
  97. package/src/app-updates/app-updates-settings.tsx +0 -136
  98. package/src/app-updates/remote-app-update-panel.test.tsx +0 -205
  99. package/src/app-updates/remote-app-update-panel.tsx +0 -210
  100. package/src/app-updates/use-app-update-status.ts +0 -16
  101. package/src/command-palette/command-palette-ui.tsx +0 -564
  102. package/src/command-palette/command-palette.ts +0 -242
  103. package/src/components/access-level-field.test.tsx +0 -32
  104. package/src/components/access-level-field.tsx +0 -68
  105. package/src/components/checkbox.tsx +0 -25
  106. package/src/components/group-switcher.tsx +0 -526
  107. package/src/components/input-date.tsx +0 -29
  108. package/src/components/input-number.tsx +0 -83
  109. package/src/components/input.tsx +0 -25
  110. package/src/components/inverse-lazy-list.tsx +0 -107
  111. package/src/components/io-schema-values.tsx +0 -103
  112. package/src/components/io-schema.tsx +0 -259
  113. package/src/components/lazy-list.tsx +0 -115
  114. package/src/components/list-screen.tsx +0 -126
  115. package/src/components/loading-indicator.tsx +0 -7
  116. package/src/components/markdown-editor/autolink-plugin.tsx +0 -29
  117. package/src/components/markdown-editor/editor-inline.tsx +0 -9
  118. package/src/components/markdown-editor/editor.tsx +0 -158
  119. package/src/components/markdown-editor/markdown-plugin.tsx +0 -202
  120. package/src/components/markdown-editor/mention-node.ts +0 -204
  121. package/src/components/markdown-editor/mentions-plugin.tsx +0 -354
  122. package/src/components/markdown-editor/move-line-plugin.tsx +0 -186
  123. package/src/components/markdown-editor/select-line-boundary-plugin.tsx +0 -95
  124. package/src/components/markdown-editor/theme.ts +0 -46
  125. package/src/components/markdown-editor/toolbar.tsx +0 -269
  126. package/src/components/markdown-with-mentions.tsx +0 -198
  127. package/src/components/message-logs/message-logs.tsx +0 -523
  128. package/src/components/message-logs/tool-access-approval-card.test.tsx +0 -127
  129. package/src/components/message-logs/tool-access-approval-card.tsx +0 -178
  130. package/src/components/messages/avatar.tsx +0 -171
  131. package/src/components/messages/channel-message-list.tsx +0 -189
  132. package/src/components/messages/channel-view.tsx +0 -92
  133. package/src/components/messages/message-compose.tsx +0 -192
  134. package/src/components/messages/message-display.tsx +0 -231
  135. package/src/components/messages/thread-message-list.tsx +0 -135
  136. package/src/components/qr-scanner.tsx +0 -180
  137. package/src/components/responsive-section-nav.test.tsx +0 -88
  138. package/src/components/responsive-section-nav.tsx +0 -92
  139. package/src/components/router.tsx +0 -175
  140. package/src/components/save-button.tsx +0 -134
  141. package/src/components/sortable-list.tsx +0 -130
  142. package/src/components/tabs.tsx +0 -76
  143. package/src/components/tooltip.tsx +0 -43
  144. package/src/components/trust-level-badge.tsx +0 -44
  145. package/src/components/trust-level-dropdown.tsx +0 -136
  146. package/src/components/typeahead.tsx +0 -357
  147. package/src/globals-hash.test.ts +0 -19
  148. package/src/globals.tsx +0 -226
  149. package/src/hooks/index.ts +0 -8
  150. package/src/hooks/use-host-contract.ts +0 -75
  151. package/src/hooks/use-observable-state.test.tsx +0 -101
  152. package/src/hooks/use-observable-state.ts +0 -29
  153. package/src/hooks/use-observable.test.tsx +0 -258
  154. package/src/hooks/use-observable.ts +0 -67
  155. package/src/hooks/use-on-screen.ts +0 -24
  156. package/src/hooks/use-promise.ts +0 -34
  157. package/src/hooks/use-rows.test.tsx +0 -196
  158. package/src/hooks/use-rows.ts +0 -120
  159. package/src/hooks/use-subscription.ts +0 -24
  160. package/src/hooks/use-tool.test.tsx +0 -225
  161. package/src/hooks/use-tool.ts +0 -116
  162. package/src/host-shell/host-shell-consumer.test.ts +0 -32
  163. package/src/host-shell/host-shell-consumer.ts +0 -53
  164. package/src/index.tsx +0 -42
  165. package/src/mention-configs.ts +0 -185
  166. package/src/operator-console/operator-console-state.ts +0 -119
  167. package/src/operator-console/operator-console.tsx +0 -233
  168. package/src/peers-services-registration.ts +0 -6
  169. package/src/root-layout/global-overlays.ts +0 -50
  170. package/src/root-layout/layout-picker.tsx +0 -47
  171. package/src/root-layout/layouts/full-screen-layout.tsx +0 -144
  172. package/src/root-layout/layouts/tabs-with-console-layout.tsx +0 -260
  173. package/src/root-layout/root-layout-host.test.tsx +0 -108
  174. package/src/root-layout/root-layout-host.tsx +0 -147
  175. package/src/root-layout/root-layout-registry.ts +0 -51
  176. package/src/root-layout/root-layout-state.ts +0 -55
  177. package/src/root-layout/root-layout.ts +0 -32
  178. package/src/screens/account/account-screen.test.tsx +0 -65
  179. package/src/screens/account/account-screen.tsx +0 -942
  180. package/src/screens/assistants/assistant-config.test.tsx +0 -54
  181. package/src/screens/assistants/assistant-config.tsx +0 -121
  182. package/src/screens/assistants/assistant-details.tsx +0 -89
  183. package/src/screens/assistants/assistant-info.tsx +0 -66
  184. package/src/screens/assistants/assistant-list.tsx +0 -145
  185. package/src/screens/assistants/assistant-tools.tsx +0 -82
  186. package/src/screens/console-logs/console-logs-list.tsx +0 -374
  187. package/src/screens/console-logs/log-display.tsx +0 -137
  188. package/src/screens/console-logs/log-filters.tsx +0 -64
  189. package/src/screens/console-logs/mobile-log-card.tsx +0 -161
  190. package/src/screens/console-logs/resizable-table-header.tsx +0 -110
  191. package/src/screens/contacts/contact-details.tsx +0 -123
  192. package/src/screens/contacts/contact-list.test.tsx +0 -59
  193. package/src/screens/contacts/contact-list.tsx +0 -286
  194. package/src/screens/contacts/index.ts +0 -2
  195. package/src/screens/data-explorer/data-explorer.tsx +0 -633
  196. package/src/screens/data-explorer/index.ts +0 -6
  197. package/src/screens/data-explorer/query-executor.tsx +0 -520
  198. package/src/screens/device-pairing/device-pairing.test.tsx +0 -143
  199. package/src/screens/device-pairing/device-pairing.tsx +0 -461
  200. package/src/screens/device-pairing/identity-ipc.ts +0 -25
  201. package/src/screens/device-pairing/index.ts +0 -2
  202. package/src/screens/device-pairing/use-device-pairing-state.ts +0 -15
  203. package/src/screens/devices/devices-list.test.tsx +0 -68
  204. package/src/screens/devices/devices-list.tsx +0 -365
  205. package/src/screens/devices/index.ts +0 -1
  206. package/src/screens/groups/group-details.tsx +0 -160
  207. package/src/screens/groups/group-invite-panel.tsx +0 -311
  208. package/src/screens/groups/group-list.test.tsx +0 -44
  209. package/src/screens/groups/group-list.tsx +0 -317
  210. package/src/screens/groups/group-members.tsx +0 -343
  211. package/src/screens/groups/index.ts +0 -3
  212. package/src/screens/identity/identity-routes.test.ts +0 -70
  213. package/src/screens/identity/identity-routes.ts +0 -301
  214. package/src/screens/identity/identity-screen.test.tsx +0 -138
  215. package/src/screens/identity/identity-screen.tsx +0 -282
  216. package/src/screens/identity/index.ts +0 -2
  217. package/src/screens/identity/invite-contact-panel.tsx +0 -45
  218. package/src/screens/identity/profile-invite.tsx +0 -93
  219. package/src/screens/invites/accept-invite.test.tsx +0 -115
  220. package/src/screens/invites/accept-invite.tsx +0 -318
  221. package/src/screens/invites/activity-view.test.tsx +0 -127
  222. package/src/screens/invites/activity-view.tsx +0 -162
  223. package/src/screens/invites/index.ts +0 -6
  224. package/src/screens/invites/invite-capture.ts +0 -104
  225. package/src/screens/invites/invite-classify.test.ts +0 -129
  226. package/src/screens/invites/invite-share.tsx +0 -104
  227. package/src/screens/invites/invites-ipc.ts +0 -25
  228. package/src/screens/invites/pending-invites-list.tsx +0 -256
  229. package/src/screens/network-viewer/connection-troubleshooter.tsx +0 -395
  230. package/src/screens/network-viewer/cpu-usage-graph.tsx +0 -294
  231. package/src/screens/network-viewer/device-details-modal.tsx +0 -339
  232. package/src/screens/network-viewer/group-details-modal.tsx +0 -421
  233. package/src/screens/network-viewer/index.ts +0 -8
  234. package/src/screens/network-viewer/network-viewer-ipc.test.ts +0 -72
  235. package/src/screens/network-viewer/network-viewer-ipc.ts +0 -39
  236. package/src/screens/network-viewer/network-viewer-section.ts +0 -12
  237. package/src/screens/network-viewer/network-viewer.section.test.tsx +0 -68
  238. package/src/screens/network-viewer/network-viewer.tsx +0 -708
  239. package/src/screens/network-viewer/remote-logs-panel.tsx +0 -190
  240. package/src/screens/network-viewer/sync-recovery.test.tsx +0 -114
  241. package/src/screens/network-viewer/sync-recovery.tsx +0 -461
  242. package/src/screens/network-viewer/usage-graph.tsx +0 -455
  243. package/src/screens/packages/local-folder-grants-panel.tsx +0 -290
  244. package/src/screens/packages/local-folders-ipc.ts +0 -28
  245. package/src/screens/packages/package-details.tsx +0 -166
  246. package/src/screens/packages/package-helpers.test.ts +0 -57
  247. package/src/screens/packages/package-helpers.ts +0 -137
  248. package/src/screens/packages/package-info.tsx +0 -354
  249. package/src/screens/packages/package-list.tsx +0 -519
  250. package/src/screens/packages/package-network-panel.tsx +0 -82
  251. package/src/screens/packages/package-new-local.tsx +0 -100
  252. package/src/screens/packages/package-runtime-status.test.tsx +0 -58
  253. package/src/screens/packages/package-runtime-status.tsx +0 -68
  254. package/src/screens/packages/package-versions.tsx +0 -298
  255. package/src/screens/packages/packages-ipc.test.ts +0 -47
  256. package/src/screens/packages/packages-ipc.ts +0 -125
  257. package/src/screens/peer-types/peer-type-details.tsx +0 -172
  258. package/src/screens/peer-types/peer-type-list.tsx +0 -64
  259. package/src/screens/search/global-search.tsx +0 -406
  260. package/src/screens/settings/color-mode-dropdown.tsx +0 -82
  261. package/src/screens/settings/settings-page.test.tsx +0 -24
  262. package/src/screens/settings/settings-page.tsx +0 -426
  263. package/src/screens/setup-user.tsx +0 -355
  264. package/src/screens/tools/tool-access-ipc.test.ts +0 -55
  265. package/src/screens/tools/tool-access-ipc.ts +0 -27
  266. package/src/screens/tools/tool-catalog-ipc.test.ts +0 -52
  267. package/src/screens/tools/tool-catalog-ipc.ts +0 -51
  268. package/src/screens/tools/tool-details.tsx +0 -52
  269. package/src/screens/tools/tool-info.tsx +0 -45
  270. package/src/screens/tools/tool-list.tsx +0 -106
  271. package/src/screens/tools/tool-schema.tsx +0 -44
  272. package/src/screens/tools/tool-test-details.tsx +0 -89
  273. package/src/screens/tools/tool-test-list.tsx +0 -73
  274. package/src/screens/variables/variable-details.tsx +0 -289
  275. package/src/screens/variables/variable-list.tsx +0 -110
  276. package/src/screens/welcome-modal.tsx +0 -294
  277. package/src/screens/workflows/workflow-details.tsx +0 -115
  278. package/src/screens/workflows/workflow-info.tsx +0 -22
  279. package/src/screens/workflows/workflow-instructions.tsx +0 -134
  280. package/src/screens/workflows/workflow-list.tsx +0 -117
  281. package/src/setupTests.ts +0 -38
  282. package/src/system-apps/assistants.app.ts +0 -7
  283. package/src/system-apps/console-logs.app.ts +0 -7
  284. package/src/system-apps/data-explorer.app.ts +0 -8
  285. package/src/system-apps/identity.app.ts +0 -9
  286. package/src/system-apps/index.ts +0 -77
  287. package/src/system-apps/mobile-settings.app.ts +0 -7
  288. package/src/system-apps/network-viewer.app.ts +0 -8
  289. package/src/system-apps/packages.app.ts +0 -7
  290. package/src/system-apps/search.app.ts +0 -8
  291. package/src/system-apps/settings.app.ts +0 -7
  292. package/src/system-apps/threads.app.ts +0 -7
  293. package/src/system-apps/tools.app.ts +0 -7
  294. package/src/system-apps/types.app.ts +0 -7
  295. package/src/system-apps/variables.app.ts +0 -7
  296. package/src/system-apps/workflows.app.ts +0 -7
  297. package/src/tabs-layout/network-status-indicator.test.tsx +0 -799
  298. package/src/tabs-layout/network-status-indicator.tsx +0 -756
  299. package/src/tabs-layout/tabs-layout.tsx +0 -857
  300. package/src/tabs-layout/tabs-state.test.ts +0 -152
  301. package/src/tabs-layout/tabs-state.ts +0 -587
  302. package/src/tabs-layout/use-network-status.test.tsx +0 -58
  303. package/src/tabs-layout/use-network-status.ts +0 -68
  304. package/src/ui-defaults/index.ts +0 -2
  305. package/src/ui-defaults/list-screen.tsx +0 -94
  306. package/src/ui-defaults/markdown-field.tsx +0 -87
  307. package/src/ui-router/routes-loader.ts +0 -193
  308. package/src/ui-router/ui-loader.test.tsx +0 -108
  309. package/src/ui-router/ui-loader.tsx +0 -606
  310. package/src/utils.ts +0 -95
  311. package/tsconfig.json +0 -18
@@ -1,587 +0,0 @@
1
- import {
2
- groupDeviceVar,
3
- groupUserVar,
4
- type IAppNav,
5
- newid,
6
- type Observable,
7
- observable,
8
- } from "@peers-app/peers-sdk";
9
- import { _mainContentPath } from "../globals";
10
- import {
11
- canonicalizeRoute,
12
- IDENTITY_APP_PATH,
13
- isIdentityPath,
14
- parseRoute,
15
- } from "../screens/identity/identity-routes";
16
- import { systemPackage } from "../system-apps";
17
- import { allPackages, type IPackageWithNavs } from "../ui-router/routes-loader";
18
-
19
- /** One top-level app tab. */
20
- export interface TabState {
21
- tabId: string;
22
- packageId?: string; // 'launcher' for app launcher tab
23
- path: string;
24
- title: string;
25
- iconClassName?: string;
26
- }
27
-
28
- /** The app launcher tab. It is always present and cannot be closed. */
29
- export const launcherApp: TabState = {
30
- tabId: "launcher",
31
- packageId: "launcher",
32
- path: "",
33
- title: "Apps",
34
- iconClassName: "bi-grid-3x3-gap",
35
- };
36
-
37
- // Persistent vars for storage (write-only, no subscription to avoid oscillation)
38
- const _persistentActiveTabs = groupDeviceVar<TabState[]>("activeTabs", {
39
- defaultValue: [launcherApp],
40
- });
41
-
42
- const _persistentActiveTabId = groupDeviceVar<string>("activeTabId", {
43
- defaultValue: "launcher",
44
- });
45
-
46
- const _persistentRecentlyUsedApps = groupUserVar<string[]>("recentlyUsedApps", {
47
- defaultValue: [],
48
- });
49
-
50
- let finishNavigationRestore: () => void = () => {};
51
- const navigationReady = new Promise<void>((resolve) => {
52
- finishNavigationRestore = resolve;
53
- });
54
-
55
- // In-memory observables for UI state (loaded once, then write-through to persistent vars)
56
- /** Open tabs. Writes through to device storage after the initial restore. */
57
- export const activeTabs: Observable<TabState[]> & { loadingPromise: Promise<void> } = (() => {
58
- const obs = observable<TabState[]>([launcherApp]);
59
-
60
- // Write-through to persistent var on change
61
- obs.subscribe((value) => {
62
- _persistentActiveTabs(value);
63
- });
64
-
65
- return Object.assign(obs, { loadingPromise: navigationReady });
66
- })();
67
-
68
- /** Id of the tab whose path is showing. */
69
- export const activeTabId: Observable<string> & { loadingPromise: Promise<void> } = (() => {
70
- const obs = observable<string>("launcher");
71
-
72
- // Write-through to persistent var on change
73
- obs.subscribe((value) => {
74
- _persistentActiveTabId(value);
75
- });
76
-
77
- return Object.assign(obs, { loadingPromise: navigationReady });
78
- })();
79
-
80
- /** App roots recently opened from the launcher, newest first. */
81
- export const recentlyUsedApps: Observable<string[]> & { loadingPromise: Promise<void> } = (() => {
82
- const obs = observable<string[]>([]);
83
-
84
- // Write-through to persistent var on change
85
- obs.subscribe((value) => {
86
- _persistentRecentlyUsedApps(value);
87
- });
88
-
89
- return Object.assign(obs, { loadingPromise: navigationReady });
90
- })();
91
-
92
- void Promise.all([
93
- _persistentActiveTabs.loadingPromise,
94
- _persistentActiveTabId.loadingPromise,
95
- _persistentRecentlyUsedApps.loadingPromise,
96
- ]).then(() => {
97
- const restored = restoreNavigationState(_persistentActiveTabs(), _persistentActiveTabId());
98
- activeTabs(restored.tabs);
99
- if (activeTabId() !== restored.activeId) {
100
- activeTabId(restored.activeId);
101
- }
102
- recentlyUsedApps(migrateRecentPaths(_persistentRecentlyUsedApps()));
103
- finishNavigationRestore();
104
- });
105
-
106
- /** Tab ids whose bodies have been mounted at least once. */
107
- export const initializedTabs = new Set<string>();
108
-
109
- /** Options for {@link navigateTo}. */
110
- export interface NavigateOptions {
111
- /**
112
- * When the requested path is an app's root and that app is already open,
113
- * activate the existing tab without resetting its section.
114
- */
115
- preserveSectionOnAppRoot?: boolean;
116
- }
117
-
118
- /**
119
- * Open a path in the tab that owns its app.
120
- *
121
- * Legacy identity aliases are rewritten first. A route owned by the current
122
- * app replaces that tab's path. A route owned by another open app activates
123
- * that app's tab. Otherwise a new tab is opened.
124
- *
125
- * @param rawPath - Requested route, with or without a query string.
126
- * @param options - App-root behavior. Launcher opens preserve the current section.
127
- */
128
- export function navigateTo(rawPath: string, options?: NavigateOptions) {
129
- const requestedPathname = parseRoute(rawPath).pathname;
130
- if (!requestedPathname) {
131
- const launcher = activeTabs().find((tab) => tab.tabId === "launcher") ?? activeTabs()[0];
132
- if (launcher && activeTabId() !== launcher.tabId) {
133
- activeTabId(launcher.tabId);
134
- }
135
- return;
136
- }
137
-
138
- const next = canonicalizeRoute(rawPath);
139
- const app = determineAppFromPath(next);
140
- if (options?.preserveSectionOnAppRoot && app && isAppRootRequest(requestedPathname, app)) {
141
- const existing = findTabForApp(app);
142
- if (existing) {
143
- if (activeTabId() !== existing.tabId) {
144
- activeTabId(existing.tabId);
145
- }
146
- rememberRecentApp(existing.path);
147
- return;
148
- }
149
- }
150
-
151
- if (!app || app.package.packageId === "launcher") {
152
- openUnresolved(next);
153
- return;
154
- }
155
-
156
- const current = activeTabs().find((tab) => tab.tabId === activeTabId());
157
- const currentApp = current ? determineAppFromPath(current.path) : undefined;
158
- if (current && current.packageId !== "launcher" && currentApp && sameApp(currentApp, app)) {
159
- showTab(current.tabId, next);
160
- return;
161
- }
162
-
163
- const existing = findTabForApp(app);
164
- if (existing) {
165
- showTab(existing.tabId, next);
166
- return;
167
- }
168
-
169
- openNewTab({
170
- path: next,
171
- title: app.navItem.displayName || app.navItem.name,
172
- packageId: app.package.packageId,
173
- iconClassName: app.navItem.iconClassName,
174
- });
175
- }
176
-
177
- /**
178
- * Open an app or path from the launcher.
179
- *
180
- * Choosing an app that is already open keeps the section the user was on.
181
- *
182
- * @param path - App root or deep link.
183
- */
184
- export function goToTabPath(path: string) {
185
- navigateTo(path, { preserveSectionOnAppRoot: true });
186
- }
187
-
188
- /**
189
- * Apply a {@link mainContentPath} write to the open tabs.
190
- *
191
- * @param _oldPath - Previous route. Retained for existing call sites.
192
- * @param newPath - Requested route.
193
- * @param _setNewMainPath - Direct path setter. Navigation updates the path itself.
194
- */
195
- export const handleMainPathChanged = (
196
- _oldPath: string,
197
- newPath: string,
198
- _setNewMainPath: (path: string) => void,
199
- ) => {
200
- navigateTo(newPath);
201
- };
202
-
203
- /**
204
- * Rewrite stored tabs onto canonical routes.
205
- *
206
- * Contacts, Groups, and Devices tabs from before the Identity app become one
207
- * Identity tab. The active tab is kept when it is one of those duplicates.
208
- *
209
- * @param tabs - Persisted tabs.
210
- * @param activeId - Persisted active tab id.
211
- */
212
- export function restoreNavigationState(
213
- tabs: TabState[],
214
- activeId: string,
215
- ): { tabs: TabState[]; activeId: string } {
216
- const migrated = (tabs.length > 0 ? tabs : [launcherApp]).map(migrateStoredTab);
217
- const merged = mergeIdentityTabs(migrated, activeId);
218
- const activeExists = merged.tabs.some((tab) => tab.tabId === merged.activeId);
219
- return {
220
- tabs: merged.tabs,
221
- activeId: activeExists ? merged.activeId : merged.tabs[0].tabId,
222
- };
223
- }
224
-
225
- /**
226
- * Map a stored recent-app path onto the current app root.
227
- *
228
- * @param path - Persisted recent path, possibly a legacy alias.
229
- */
230
- export function migrateRecentPath(path: string): string {
231
- const canonical = canonicalizeRoute(path);
232
- if (isIdentityPath(canonical)) {
233
- return IDENTITY_APP_PATH;
234
- }
235
- return canonical;
236
- }
237
-
238
- type AppInfo = { navItem: IAppNav; package: IPackageWithNavs };
239
-
240
- /**
241
- * Resolve the app that owns a route.
242
- *
243
- * @param path - Route, with or without a query string.
244
- * @returns The matching app, the launcher for an empty path, or `undefined`.
245
- */
246
- export function determineAppFromPath(path: string): AppInfo | undefined {
247
- const launcherInfo = {
248
- navItem: {
249
- name: "Apps",
250
- navigationPath: "",
251
- iconClassName: launcherApp.iconClassName || "bi-grid-3x3-gap",
252
- },
253
- package: systemPackage,
254
- };
255
- if (!path || path === "" || path === "/") {
256
- return launcherInfo;
257
- }
258
-
259
- // Remove leading/trailing slashes and query params
260
- path = path.toLowerCase().trim().split("?")[0];
261
- if (path.startsWith("/")) {
262
- path = path.substring(1);
263
- }
264
- const rootPath = path.split("/")[0];
265
- const _allPackages = [systemPackage, ...allPackages()]; // WARNING allPackages might not be loaded yet
266
-
267
- const allAppInfos: AppInfo[] = _allPackages
268
- .filter((pkg) => (pkg.appNavs?.length ?? 0) > 0)
269
- .flatMap((pkg) => {
270
- const navs = pkg.appNavs;
271
- if (!navs) return [];
272
- return navs.map((navItem) => ({ navItem, package: pkg }));
273
- });
274
-
275
- // Find the most relevant app based on path
276
- for (const appInfo of allAppInfos) {
277
- const navPath = appInfo.navItem.navigationPath || appInfo.navItem.name.toLowerCase();
278
-
279
- // Check for exact match or path starting with nav path
280
- if (rootPath === navPath || rootPath.startsWith(`${navPath}/`)) {
281
- // Return the first matching app
282
- return {
283
- navItem: appInfo.navItem,
284
- package: appInfo.package,
285
- };
286
- }
287
- }
288
-
289
- // look for package-specific routes
290
- // uri in format of "package-nav/{packageId}/{path}"
291
- const packageNavPrefix = "package-nav/";
292
- if (path.startsWith(packageNavPrefix)) {
293
- const parts = path.substring(packageNavPrefix.length).split("/");
294
- if (parts.length > 0) {
295
- const packageId = parts[0];
296
- const pkg = _allPackages.find((p) => p.packageId === packageId);
297
- if (pkg) {
298
- const subPath = parts.slice(1).join("/");
299
- const navItem = pkg.appNavs?.find((n) => {
300
- const navPath = n.navigationPath || n.name.toLowerCase();
301
- return subPath === navPath || subPath.startsWith(`${navPath}/`);
302
- });
303
- if (navItem) {
304
- return {
305
- navItem,
306
- package: pkg,
307
- };
308
- }
309
- }
310
- }
311
- }
312
-
313
- console.warn(`No app found for path: ${path}`);
314
- return undefined;
315
- }
316
-
317
- function appKey(app: AppInfo): string {
318
- const nav = app.navItem.navigationPath || app.navItem.name.toLowerCase();
319
- return `${app.package.packageId}:${nav}`;
320
- }
321
-
322
- function sameApp(left: AppInfo, right: AppInfo): boolean {
323
- return appKey(left) === appKey(right);
324
- }
325
-
326
- function isAppRootRequest(pathname: string, app: AppInfo): boolean {
327
- const nav = (app.navItem.navigationPath || app.navItem.name).toLowerCase();
328
- return pathname.toLowerCase() === nav;
329
- }
330
-
331
- function findTabForApp(app: AppInfo): TabState | undefined {
332
- const key = appKey(app);
333
- return activeTabs().find((tab) => {
334
- const info = determineAppFromPath(tab.path);
335
- return info ? appKey(info) === key : false;
336
- });
337
- }
338
-
339
- /**
340
- * Replace a tab's path immutably so the memoized tab body re-renders.
341
- * Activating a different tab lets the tab subscription publish the path.
342
- */
343
- function showTab(tabId: string, path: string) {
344
- const tabs = activeTabs();
345
- const index = tabs.findIndex((tab) => tab.tabId === tabId);
346
- if (index < 0) {
347
- return;
348
- }
349
- const current = tabs[index];
350
- if (current.path !== path) {
351
- const nextTabs = [...tabs];
352
- nextTabs[index] = { ...current, path };
353
- activeTabs(nextTabs);
354
- }
355
- if (activeTabId() !== tabId) {
356
- activeTabId(tabId);
357
- } else if (_mainContentPath() !== path) {
358
- _mainContentPath(path);
359
- }
360
- rememberRecentApp(path);
361
- }
362
-
363
- function openUnresolved(path: string) {
364
- const current = activeTabs().find((tab) => tab.tabId === activeTabId());
365
- if (current && current.packageId === "system-apps") {
366
- showTab(current.tabId, path);
367
- return;
368
- }
369
- openNewTab({
370
- path,
371
- title: "New Tab",
372
- packageId: "system-apps",
373
- iconClassName: "bi-question-circle",
374
- });
375
- }
376
-
377
- function rememberRecentApp(path: string) {
378
- const app = determineAppFromPath(canonicalizeRoute(path));
379
- if (!app || app.package.packageId === "launcher") {
380
- return;
381
- }
382
- const recentPath =
383
- app.package.packageId === systemPackage.packageId ? app.navItem.navigationPath || path : path;
384
- if (!recentPath) {
385
- return;
386
- }
387
- const recent = recentlyUsedApps();
388
- const filtered = recent.filter((entry) => entry !== recentPath);
389
- const next = [recentPath, ...filtered].slice(0, 8);
390
- if (next.length === recent.length && next.every((entry, index) => entry === recent[index])) {
391
- return;
392
- }
393
- recentlyUsedApps(next);
394
- }
395
-
396
- function migrateStoredTab(tab: TabState): TabState {
397
- const path = canonicalizeRoute(tab.path);
398
- if (path === tab.path) {
399
- return tab;
400
- }
401
- const app = determineAppFromPath(path);
402
- const identity = app?.navItem.navigationPath === IDENTITY_APP_PATH;
403
- return {
404
- ...tab,
405
- path,
406
- packageId: app?.package.packageId ?? tab.packageId,
407
- title: identity ? app?.navItem.displayName || app?.navItem.name || tab.title : tab.title,
408
- iconClassName: identity ? app?.navItem.iconClassName || tab.iconClassName : tab.iconClassName,
409
- };
410
- }
411
-
412
- function mergeIdentityTabs(
413
- tabs: TabState[],
414
- activeId: string,
415
- ): { tabs: TabState[]; activeId: string } {
416
- const identityTabs = tabs.filter((tab) => isIdentityPath(tab.path));
417
- if (identityTabs.length <= 1) {
418
- return { tabs, activeId };
419
- }
420
- const keeper = identityTabs.find((tab) => tab.tabId === activeId) ?? identityTabs[0];
421
- const remove = new Set(
422
- identityTabs.filter((tab) => tab.tabId !== keeper.tabId).map((tab) => tab.tabId),
423
- );
424
- return {
425
- tabs: tabs.filter((tab) => !remove.has(tab.tabId)),
426
- activeId: remove.has(activeId) ? keeper.tabId : activeId,
427
- };
428
- }
429
-
430
- function migrateRecentPaths(paths: string[]): string[] {
431
- const seen = new Set<string>();
432
- const next: string[] = [];
433
- for (const path of paths) {
434
- const migrated = migrateRecentPath(path);
435
- if (!migrated || seen.has(migrated)) {
436
- continue;
437
- }
438
- seen.add(migrated);
439
- next.push(migrated);
440
- }
441
- return next.slice(0, 8);
442
- }
443
-
444
- /**
445
- * Set the active tab's title.
446
- *
447
- * @param newTitle - Title shown on the tab strip.
448
- */
449
- export const updateActiveTabTitle = (newTitle: string) => {
450
- // TODO Fix this to use a more reliable update mechanism
451
- // using setTimeout to get around a race condition for setting title on new tabs
452
- setTimeout(() => {
453
- const currentTabs = activeTabs();
454
- const activeTab = currentTabs.find((t) => t.tabId === activeTabId());
455
- if (activeTab) {
456
- activeTab.title = newTitle;
457
- activeTabs([...currentTabs]);
458
- }
459
- }, 100);
460
- };
461
-
462
- // Single-tab mode: when enabled, opening an app replaces the current screen instead of
463
- // appending a tab. Used by the full-screen root layout so it shows one screen at a time.
464
- let singleTabModeEnabled = false;
465
-
466
- /**
467
- * Enable or disable single-tab mode.
468
- *
469
- * When enabled, {@link openNewTab} replaces the active (non-launcher) screen in place
470
- * rather than appending a new tab, so single-screen layouts (e.g. full-screen) never
471
- * accumulate tabs. This is non-destructive: any pre-existing tabs are left untouched and
472
- * reappear when a multi-tab layout becomes active again. Layouts should enable it on mount
473
- * and disable it on unmount.
474
- */
475
- export const setSingleTabMode = (enabled: boolean) => {
476
- singleTabModeEnabled = enabled;
477
- };
478
-
479
- /**
480
- * Open a tab, or activate an existing tab with the same package and path.
481
- *
482
- * @param tab - Tab contents. `tabId` is assigned here.
483
- * @param forceNew - When true, always append a tab instead of reusing one.
484
- */
485
- export const openNewTab = (tab: Omit<TabState, "tabId">, forceNew = false) => {
486
- const newTab: TabState = {
487
- ...tab,
488
- tabId: newid(),
489
- };
490
-
491
- const currentTabs = activeTabs();
492
- const existingTabIndex = currentTabs.findIndex(
493
- (t) => t.packageId === tab.packageId && t.path === tab.path,
494
- );
495
-
496
- if (existingTabIndex >= 0 && !forceNew) {
497
- // Switch to existing tab
498
- activeTabId(currentTabs[existingTabIndex].tabId);
499
- } else if (singleTabModeEnabled) {
500
- // Single-tab mode: replace the active non-launcher screen in place. From the launcher
501
- // (home), append once so the launcher stays put and the new app becomes the single
502
- // app slot.
503
- const activeIndex = currentTabs.findIndex((t) => t.tabId === activeTabId());
504
- const canReplace = activeIndex >= 0 && currentTabs[activeIndex].tabId !== "launcher";
505
- if (canReplace) {
506
- const newTabs = [...currentTabs];
507
- newTabs[activeIndex] = newTab;
508
- activeTabs(newTabs);
509
- } else {
510
- activeTabs([...currentTabs, newTab]);
511
- }
512
- activeTabId(newTab.tabId);
513
- } else {
514
- // Always add new tab (for app launcher or when forceNew is true)
515
- activeTabs([...currentTabs, newTab]);
516
- activeTabId(newTab.tabId);
517
- }
518
-
519
- if (tab.path && tab.packageId !== "launcher") {
520
- rememberRecentApp(tab.path);
521
- }
522
- };
523
-
524
- /** Close the active tab. The launcher tab stays open. */
525
- export const closeCurrentTab = () => {
526
- const currentTabs = activeTabs();
527
- const _activeTabId = activeTabId();
528
- const tabToClose = currentTabs.find((t) => t.tabId === _activeTabId);
529
-
530
- if (!tabToClose || tabToClose.tabId === "launcher") return;
531
-
532
- const closedIndex = currentTabs.findIndex((t) => t.tabId === _activeTabId);
533
- const newTabs = currentTabs.filter((t) => t.tabId !== _activeTabId);
534
- activeTabs(newTabs);
535
- initializedTabs.delete(_activeTabId);
536
-
537
- if (newTabs.length === 0) {
538
- activeTabId("launcher");
539
- } else if (closedIndex < newTabs.length) {
540
- activeTabId(newTabs[closedIndex].tabId);
541
- } else {
542
- activeTabId(newTabs[newTabs.length - 1].tabId);
543
- }
544
- };
545
-
546
- /** Activate the next open tab, wrapping around. */
547
- export const switchToNextTab = () => {
548
- const currentTabs = activeTabs();
549
- const activeTab = activeTabId();
550
- const currentIndex = currentTabs.findIndex((tab) => tab.tabId === activeTab);
551
-
552
- if (currentIndex === -1) return;
553
-
554
- const nextIndex = currentIndex === currentTabs.length - 1 ? 0 : currentIndex + 1;
555
- activeTabId(currentTabs[nextIndex].tabId);
556
- };
557
-
558
- /** Activate the previous open tab, wrapping around. */
559
- export const switchToPreviousTab = () => {
560
- const currentTabs = activeTabs();
561
- const activeTab = activeTabId();
562
- const currentIndex = currentTabs.findIndex((tab) => tab.tabId === activeTab);
563
-
564
- if (currentIndex === -1) return;
565
-
566
- const prevIndex = currentIndex === 0 ? currentTabs.length - 1 : currentIndex - 1;
567
- activeTabId(currentTabs[prevIndex].tabId);
568
- };
569
-
570
- // Keyboard shortcuts are now handled by the command palette system
571
-
572
- activeTabId.subscribe((newActiveTab) => {
573
- const currentTabs = activeTabs();
574
- const activeTab = currentTabs.find((t) => t.tabId === newActiveTab);
575
-
576
- if (!activeTab) {
577
- console.warn("Active tab not found:", newActiveTab);
578
- return;
579
- }
580
-
581
- // Update mainContentPath for the new active tab
582
- if (activeTab.packageId !== "launcher") {
583
- _mainContentPath(activeTab.path);
584
- } else {
585
- _mainContentPath(""); // Launcher or fallback
586
- }
587
- });
@@ -1,58 +0,0 @@
1
- import {
2
- type INetworkDiagnosticsConsumer,
3
- type INetworkStatusSummary,
4
- inProcessTransportPair,
5
- observable,
6
- registerHostContractTransport,
7
- } from "@peers-app/peers-sdk";
8
- import { act, renderHook, waitFor } from "@testing-library/react";
9
- import { getNetworkDiagnosticsConsumer } from "../screens/network-viewer/network-viewer-ipc";
10
- import { useNetworkStatus } from "./use-network-status";
11
-
12
- jest.mock("../screens/network-viewer/network-viewer-ipc", () => ({
13
- getNetworkDiagnosticsConsumer: jest.fn(),
14
- }));
15
-
16
- const mockGetDiagnostics = getNetworkDiagnosticsConsumer as jest.MockedFunction<
17
- typeof getNetworkDiagnosticsConsumer
18
- >;
19
-
20
- function makeStatus(overrides: Partial<INetworkStatusSummary> = {}): INetworkStatusSummary {
21
- return {
22
- state: "offline",
23
- connectionCount: 0,
24
- syncingContextCount: 0,
25
- errorCount: 0,
26
- disconnectedContextCount: 0,
27
- connectedContextCount: 0,
28
- stuckRecoveryCount: 0,
29
- errors: [],
30
- ...overrides,
31
- };
32
- }
33
-
34
- describe("useNetworkStatus", () => {
35
- it("streams status updates and reports host disconnection", async () => {
36
- const status = observable(makeStatus());
37
- mockGetDiagnostics.mockResolvedValue({
38
- observables: { status },
39
- } as INetworkDiagnosticsConsumer);
40
- const [transport] = inProcessTransportPair();
41
- const disposeTransport = registerHostContractTransport(transport);
42
- const hook = renderHook(() => useNetworkStatus());
43
-
44
- await waitFor(() => expect(hook.result.current.availability).toBe("ready"));
45
- expect(hook.result.current.status?.state).toBe("offline");
46
-
47
- act(() => {
48
- status(makeStatus({ state: "syncing", connectionCount: 2, syncingContextCount: 1 }));
49
- });
50
- await waitFor(() => expect(hook.result.current.status?.state).toBe("syncing"));
51
-
52
- act(() => {
53
- disposeTransport();
54
- });
55
- await waitFor(() => expect(hook.result.current.availability).toBe("unavailable"));
56
- hook.unmount();
57
- });
58
- });
@@ -1,68 +0,0 @@
1
- import {
2
- type INetworkStatusSummary,
3
- type Subscription,
4
- subscribeHostContractTransport,
5
- } from "@peers-app/peers-sdk";
6
- import { useEffect, useState } from "react";
7
- import { getNetworkDiagnosticsConsumer } from "../screens/network-viewer/network-viewer-ipc";
8
-
9
- /** Host availability plus the latest reactive network status snapshot. */
10
- export interface INetworkStatusViewState {
11
- availability: "loading" | "ready" | "unavailable";
12
- status?: INetworkStatusSummary;
13
- }
14
-
15
- /** Subscribe to the current host's System Network Diagnostics status observable. */
16
- export function useNetworkStatus(): INetworkStatusViewState {
17
- const [viewState, setViewState] = useState<INetworkStatusViewState>({
18
- availability: "loading",
19
- });
20
-
21
- useEffect(() => {
22
- let active = true;
23
- let generation = 0;
24
- let statusSubscription: Subscription | undefined;
25
- let hadTransport = false;
26
- const unsubscribeTransport = subscribeHostContractTransport((transport) => {
27
- generation += 1;
28
- const currentGeneration = generation;
29
- statusSubscription?.dispose();
30
- statusSubscription = undefined;
31
- if (!transport) {
32
- setViewState({ availability: hadTransport ? "unavailable" : "loading" });
33
- return;
34
- }
35
- hadTransport = true;
36
- setViewState({ availability: "loading" });
37
- void getNetworkDiagnosticsConsumer()
38
- .then((diagnostics) => {
39
- if (!active || generation !== currentGeneration) {
40
- return;
41
- }
42
- setViewState({
43
- availability: "ready",
44
- status: diagnostics.observables.status(),
45
- });
46
- statusSubscription = diagnostics.observables.status.subscribe((status) => {
47
- if (active && generation === currentGeneration) {
48
- setViewState({ availability: "ready", status });
49
- }
50
- });
51
- })
52
- .catch(() => {
53
- if (active && generation === currentGeneration) {
54
- setViewState({ availability: "unavailable" });
55
- }
56
- });
57
- });
58
-
59
- return () => {
60
- active = false;
61
- generation += 1;
62
- statusSubscription?.dispose();
63
- unsubscribeTransport();
64
- };
65
- }, []);
66
-
67
- return viewState;
68
- }