@peers-app/peers-ui 0.27.2 → 0.27.4

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 (309) hide show
  1. package/dist/screens/network-viewer/network-viewer-ipc.d.ts +3 -1
  2. package/dist/screens/network-viewer/network-viewer-ipc.js +6 -0
  3. package/dist/screens/network-viewer/sync-recovery.js +103 -1
  4. package/dist/screens/variables/variable-details.js +2 -1
  5. package/dist/tabs-layout/network-status-indicator.js +26 -12
  6. package/dist/tabs-layout/use-network-status.d.ts +6 -1
  7. package/dist/tabs-layout/use-network-status.js +27 -4
  8. package/package.json +9 -3
  9. package/.claude/settings.local.json +0 -34
  10. package/.github/README.md +0 -52
  11. package/.github/workflows/delete-all-artifacts.yml +0 -49
  12. package/.github/workflows/publish.yml +0 -51
  13. package/babel.config.js +0 -7
  14. package/biome.json +0 -198
  15. package/dist/app-updates/app-updates-consumer.test.d.ts +0 -1
  16. package/dist/app-updates/app-updates-consumer.test.js +0 -56
  17. package/dist/app-updates/app-updates-settings.test.d.ts +0 -1
  18. package/dist/app-updates/app-updates-settings.test.js +0 -93
  19. package/dist/app-updates/remote-app-update-panel.test.d.ts +0 -1
  20. package/dist/app-updates/remote-app-update-panel.test.js +0 -165
  21. package/dist/components/access-level-field.test.d.ts +0 -1
  22. package/dist/components/access-level-field.test.js +0 -28
  23. package/dist/components/message-logs/tool-access-approval-card.test.d.ts +0 -1
  24. package/dist/components/message-logs/tool-access-approval-card.test.js +0 -102
  25. package/dist/components/responsive-section-nav.test.d.ts +0 -1
  26. package/dist/components/responsive-section-nav.test.js +0 -45
  27. package/dist/globals-hash.test.d.ts +0 -1
  28. package/dist/globals-hash.test.js +0 -16
  29. package/dist/hooks/use-observable-state.test.d.ts +0 -1
  30. package/dist/hooks/use-observable-state.test.js +0 -64
  31. package/dist/hooks/use-observable.test.d.ts +0 -1
  32. package/dist/hooks/use-observable.test.js +0 -191
  33. package/dist/hooks/use-rows.test.d.ts +0 -1
  34. package/dist/hooks/use-rows.test.js +0 -175
  35. package/dist/hooks/use-tool.test.d.ts +0 -1
  36. package/dist/hooks/use-tool.test.js +0 -190
  37. package/dist/host-shell/host-shell-consumer.test.d.ts +0 -1
  38. package/dist/host-shell/host-shell-consumer.test.js +0 -28
  39. package/dist/root-layout/root-layout-host.test.d.ts +0 -1
  40. package/dist/root-layout/root-layout-host.test.js +0 -86
  41. package/dist/screens/account/account-screen.test.d.ts +0 -1
  42. package/dist/screens/account/account-screen.test.js +0 -51
  43. package/dist/screens/assistants/assistant-config.test.d.ts +0 -1
  44. package/dist/screens/assistants/assistant-config.test.js +0 -57
  45. package/dist/screens/contacts/contact-list.test.d.ts +0 -1
  46. package/dist/screens/contacts/contact-list.test.js +0 -48
  47. package/dist/screens/device-pairing/device-pairing.test.d.ts +0 -1
  48. package/dist/screens/device-pairing/device-pairing.test.js +0 -119
  49. package/dist/screens/devices/devices-list.test.d.ts +0 -1
  50. package/dist/screens/devices/devices-list.test.js +0 -53
  51. package/dist/screens/groups/group-list.test.d.ts +0 -1
  52. package/dist/screens/groups/group-list.test.js +0 -38
  53. package/dist/screens/identity/identity-routes.test.d.ts +0 -1
  54. package/dist/screens/identity/identity-routes.test.js +0 -51
  55. package/dist/screens/identity/identity-screen.test.d.ts +0 -1
  56. package/dist/screens/identity/identity-screen.test.js +0 -103
  57. package/dist/screens/invites/accept-invite.test.d.ts +0 -1
  58. package/dist/screens/invites/accept-invite.test.js +0 -103
  59. package/dist/screens/invites/activity-view.test.d.ts +0 -1
  60. package/dist/screens/invites/activity-view.test.js +0 -108
  61. package/dist/screens/invites/invite-classify.test.d.ts +0 -1
  62. package/dist/screens/invites/invite-classify.test.js +0 -115
  63. package/dist/screens/network-viewer/network-viewer-ipc.test.d.ts +0 -1
  64. package/dist/screens/network-viewer/network-viewer-ipc.test.js +0 -61
  65. package/dist/screens/network-viewer/network-viewer.section.test.d.ts +0 -1
  66. package/dist/screens/network-viewer/network-viewer.section.test.js +0 -63
  67. package/dist/screens/network-viewer/sync-recovery.test.d.ts +0 -1
  68. package/dist/screens/network-viewer/sync-recovery.test.js +0 -104
  69. package/dist/screens/packages/package-helpers.test.d.ts +0 -1
  70. package/dist/screens/packages/package-helpers.test.js +0 -53
  71. package/dist/screens/packages/package-runtime-status.test.d.ts +0 -1
  72. package/dist/screens/packages/package-runtime-status.test.js +0 -50
  73. package/dist/screens/packages/packages-ipc.test.d.ts +0 -1
  74. package/dist/screens/packages/packages-ipc.test.js +0 -43
  75. package/dist/screens/settings/settings-page.test.d.ts +0 -1
  76. package/dist/screens/settings/settings-page.test.js +0 -19
  77. package/dist/screens/tools/tool-access-ipc.test.d.ts +0 -1
  78. package/dist/screens/tools/tool-access-ipc.test.js +0 -40
  79. package/dist/screens/tools/tool-catalog-ipc.test.d.ts +0 -1
  80. package/dist/screens/tools/tool-catalog-ipc.test.js +0 -52
  81. package/dist/tabs-layout/network-status-indicator.test.d.ts +0 -1
  82. package/dist/tabs-layout/network-status-indicator.test.js +0 -665
  83. package/dist/tabs-layout/tabs-state.test.d.ts +0 -1
  84. package/dist/tabs-layout/tabs-state.test.js +0 -110
  85. package/dist/tabs-layout/use-network-status.test.d.ts +0 -1
  86. package/dist/tabs-layout/use-network-status.test.js +0 -45
  87. package/dist/ui-router/ui-loader.test.d.ts +0 -1
  88. package/dist/ui-router/ui-loader.test.js +0 -91
  89. package/jest.config.js +0 -25
  90. package/release-guard.js +0 -10
  91. package/src/app-updates/app-update-controls.tsx +0 -73
  92. package/src/app-updates/app-updates-consumer.test.ts +0 -83
  93. package/src/app-updates/app-updates-consumer.ts +0 -121
  94. package/src/app-updates/app-updates-settings.test.tsx +0 -109
  95. package/src/app-updates/app-updates-settings.tsx +0 -136
  96. package/src/app-updates/remote-app-update-panel.test.tsx +0 -205
  97. package/src/app-updates/remote-app-update-panel.tsx +0 -210
  98. package/src/app-updates/use-app-update-status.ts +0 -16
  99. package/src/command-palette/command-palette-ui.tsx +0 -564
  100. package/src/command-palette/command-palette.ts +0 -242
  101. package/src/components/access-level-field.test.tsx +0 -32
  102. package/src/components/access-level-field.tsx +0 -68
  103. package/src/components/checkbox.tsx +0 -25
  104. package/src/components/group-switcher.tsx +0 -526
  105. package/src/components/input-date.tsx +0 -29
  106. package/src/components/input-number.tsx +0 -83
  107. package/src/components/input.tsx +0 -25
  108. package/src/components/inverse-lazy-list.tsx +0 -107
  109. package/src/components/io-schema-values.tsx +0 -103
  110. package/src/components/io-schema.tsx +0 -259
  111. package/src/components/lazy-list.tsx +0 -115
  112. package/src/components/list-screen.tsx +0 -126
  113. package/src/components/loading-indicator.tsx +0 -7
  114. package/src/components/markdown-editor/autolink-plugin.tsx +0 -29
  115. package/src/components/markdown-editor/editor-inline.tsx +0 -9
  116. package/src/components/markdown-editor/editor.tsx +0 -158
  117. package/src/components/markdown-editor/markdown-plugin.tsx +0 -202
  118. package/src/components/markdown-editor/mention-node.ts +0 -204
  119. package/src/components/markdown-editor/mentions-plugin.tsx +0 -354
  120. package/src/components/markdown-editor/move-line-plugin.tsx +0 -186
  121. package/src/components/markdown-editor/select-line-boundary-plugin.tsx +0 -95
  122. package/src/components/markdown-editor/theme.ts +0 -46
  123. package/src/components/markdown-editor/toolbar.tsx +0 -269
  124. package/src/components/markdown-with-mentions.tsx +0 -198
  125. package/src/components/message-logs/message-logs.tsx +0 -523
  126. package/src/components/message-logs/tool-access-approval-card.test.tsx +0 -127
  127. package/src/components/message-logs/tool-access-approval-card.tsx +0 -178
  128. package/src/components/messages/avatar.tsx +0 -171
  129. package/src/components/messages/channel-message-list.tsx +0 -189
  130. package/src/components/messages/channel-view.tsx +0 -92
  131. package/src/components/messages/message-compose.tsx +0 -192
  132. package/src/components/messages/message-display.tsx +0 -231
  133. package/src/components/messages/thread-message-list.tsx +0 -135
  134. package/src/components/qr-scanner.tsx +0 -180
  135. package/src/components/responsive-section-nav.test.tsx +0 -88
  136. package/src/components/responsive-section-nav.tsx +0 -92
  137. package/src/components/router.tsx +0 -175
  138. package/src/components/save-button.tsx +0 -134
  139. package/src/components/sortable-list.tsx +0 -130
  140. package/src/components/tabs.tsx +0 -76
  141. package/src/components/tooltip.tsx +0 -43
  142. package/src/components/trust-level-badge.tsx +0 -44
  143. package/src/components/trust-level-dropdown.tsx +0 -136
  144. package/src/components/typeahead.tsx +0 -357
  145. package/src/globals-hash.test.ts +0 -19
  146. package/src/globals.tsx +0 -226
  147. package/src/hooks/index.ts +0 -8
  148. package/src/hooks/use-host-contract.ts +0 -75
  149. package/src/hooks/use-observable-state.test.tsx +0 -101
  150. package/src/hooks/use-observable-state.ts +0 -29
  151. package/src/hooks/use-observable.test.tsx +0 -258
  152. package/src/hooks/use-observable.ts +0 -67
  153. package/src/hooks/use-on-screen.ts +0 -24
  154. package/src/hooks/use-promise.ts +0 -34
  155. package/src/hooks/use-rows.test.tsx +0 -196
  156. package/src/hooks/use-rows.ts +0 -120
  157. package/src/hooks/use-subscription.ts +0 -24
  158. package/src/hooks/use-tool.test.tsx +0 -225
  159. package/src/hooks/use-tool.ts +0 -116
  160. package/src/host-shell/host-shell-consumer.test.ts +0 -32
  161. package/src/host-shell/host-shell-consumer.ts +0 -53
  162. package/src/index.tsx +0 -42
  163. package/src/mention-configs.ts +0 -185
  164. package/src/operator-console/operator-console-state.ts +0 -119
  165. package/src/operator-console/operator-console.tsx +0 -233
  166. package/src/peers-services-registration.ts +0 -6
  167. package/src/root-layout/global-overlays.ts +0 -50
  168. package/src/root-layout/layout-picker.tsx +0 -47
  169. package/src/root-layout/layouts/full-screen-layout.tsx +0 -144
  170. package/src/root-layout/layouts/tabs-with-console-layout.tsx +0 -260
  171. package/src/root-layout/root-layout-host.test.tsx +0 -108
  172. package/src/root-layout/root-layout-host.tsx +0 -147
  173. package/src/root-layout/root-layout-registry.ts +0 -51
  174. package/src/root-layout/root-layout-state.ts +0 -55
  175. package/src/root-layout/root-layout.ts +0 -32
  176. package/src/screens/account/account-screen.test.tsx +0 -65
  177. package/src/screens/account/account-screen.tsx +0 -942
  178. package/src/screens/assistants/assistant-config.test.tsx +0 -54
  179. package/src/screens/assistants/assistant-config.tsx +0 -121
  180. package/src/screens/assistants/assistant-details.tsx +0 -89
  181. package/src/screens/assistants/assistant-info.tsx +0 -66
  182. package/src/screens/assistants/assistant-list.tsx +0 -145
  183. package/src/screens/assistants/assistant-tools.tsx +0 -82
  184. package/src/screens/console-logs/console-logs-list.tsx +0 -374
  185. package/src/screens/console-logs/log-display.tsx +0 -137
  186. package/src/screens/console-logs/log-filters.tsx +0 -64
  187. package/src/screens/console-logs/mobile-log-card.tsx +0 -161
  188. package/src/screens/console-logs/resizable-table-header.tsx +0 -110
  189. package/src/screens/contacts/contact-details.tsx +0 -123
  190. package/src/screens/contacts/contact-list.test.tsx +0 -59
  191. package/src/screens/contacts/contact-list.tsx +0 -286
  192. package/src/screens/contacts/index.ts +0 -2
  193. package/src/screens/data-explorer/data-explorer.tsx +0 -633
  194. package/src/screens/data-explorer/index.ts +0 -6
  195. package/src/screens/data-explorer/query-executor.tsx +0 -520
  196. package/src/screens/device-pairing/device-pairing.test.tsx +0 -143
  197. package/src/screens/device-pairing/device-pairing.tsx +0 -461
  198. package/src/screens/device-pairing/identity-ipc.ts +0 -25
  199. package/src/screens/device-pairing/index.ts +0 -2
  200. package/src/screens/device-pairing/use-device-pairing-state.ts +0 -15
  201. package/src/screens/devices/devices-list.test.tsx +0 -68
  202. package/src/screens/devices/devices-list.tsx +0 -365
  203. package/src/screens/devices/index.ts +0 -1
  204. package/src/screens/groups/group-details.tsx +0 -160
  205. package/src/screens/groups/group-invite-panel.tsx +0 -311
  206. package/src/screens/groups/group-list.test.tsx +0 -44
  207. package/src/screens/groups/group-list.tsx +0 -317
  208. package/src/screens/groups/group-members.tsx +0 -343
  209. package/src/screens/groups/index.ts +0 -3
  210. package/src/screens/identity/identity-routes.test.ts +0 -70
  211. package/src/screens/identity/identity-routes.ts +0 -301
  212. package/src/screens/identity/identity-screen.test.tsx +0 -138
  213. package/src/screens/identity/identity-screen.tsx +0 -282
  214. package/src/screens/identity/index.ts +0 -2
  215. package/src/screens/identity/invite-contact-panel.tsx +0 -45
  216. package/src/screens/identity/profile-invite.tsx +0 -93
  217. package/src/screens/invites/accept-invite.test.tsx +0 -115
  218. package/src/screens/invites/accept-invite.tsx +0 -318
  219. package/src/screens/invites/activity-view.test.tsx +0 -127
  220. package/src/screens/invites/activity-view.tsx +0 -162
  221. package/src/screens/invites/index.ts +0 -6
  222. package/src/screens/invites/invite-capture.ts +0 -104
  223. package/src/screens/invites/invite-classify.test.ts +0 -129
  224. package/src/screens/invites/invite-share.tsx +0 -104
  225. package/src/screens/invites/invites-ipc.ts +0 -25
  226. package/src/screens/invites/pending-invites-list.tsx +0 -256
  227. package/src/screens/network-viewer/connection-troubleshooter.tsx +0 -395
  228. package/src/screens/network-viewer/cpu-usage-graph.tsx +0 -294
  229. package/src/screens/network-viewer/device-details-modal.tsx +0 -339
  230. package/src/screens/network-viewer/group-details-modal.tsx +0 -421
  231. package/src/screens/network-viewer/index.ts +0 -8
  232. package/src/screens/network-viewer/network-viewer-ipc.test.ts +0 -72
  233. package/src/screens/network-viewer/network-viewer-ipc.ts +0 -39
  234. package/src/screens/network-viewer/network-viewer-section.ts +0 -12
  235. package/src/screens/network-viewer/network-viewer.section.test.tsx +0 -68
  236. package/src/screens/network-viewer/network-viewer.tsx +0 -708
  237. package/src/screens/network-viewer/remote-logs-panel.tsx +0 -190
  238. package/src/screens/network-viewer/sync-recovery.test.tsx +0 -114
  239. package/src/screens/network-viewer/sync-recovery.tsx +0 -461
  240. package/src/screens/network-viewer/usage-graph.tsx +0 -455
  241. package/src/screens/packages/local-folder-grants-panel.tsx +0 -290
  242. package/src/screens/packages/local-folders-ipc.ts +0 -28
  243. package/src/screens/packages/package-details.tsx +0 -166
  244. package/src/screens/packages/package-helpers.test.ts +0 -57
  245. package/src/screens/packages/package-helpers.ts +0 -137
  246. package/src/screens/packages/package-info.tsx +0 -354
  247. package/src/screens/packages/package-list.tsx +0 -519
  248. package/src/screens/packages/package-network-panel.tsx +0 -82
  249. package/src/screens/packages/package-new-local.tsx +0 -100
  250. package/src/screens/packages/package-runtime-status.test.tsx +0 -58
  251. package/src/screens/packages/package-runtime-status.tsx +0 -68
  252. package/src/screens/packages/package-versions.tsx +0 -298
  253. package/src/screens/packages/packages-ipc.test.ts +0 -47
  254. package/src/screens/packages/packages-ipc.ts +0 -125
  255. package/src/screens/peer-types/peer-type-details.tsx +0 -172
  256. package/src/screens/peer-types/peer-type-list.tsx +0 -64
  257. package/src/screens/search/global-search.tsx +0 -406
  258. package/src/screens/settings/color-mode-dropdown.tsx +0 -82
  259. package/src/screens/settings/settings-page.test.tsx +0 -24
  260. package/src/screens/settings/settings-page.tsx +0 -426
  261. package/src/screens/setup-user.tsx +0 -355
  262. package/src/screens/tools/tool-access-ipc.test.ts +0 -55
  263. package/src/screens/tools/tool-access-ipc.ts +0 -27
  264. package/src/screens/tools/tool-catalog-ipc.test.ts +0 -52
  265. package/src/screens/tools/tool-catalog-ipc.ts +0 -51
  266. package/src/screens/tools/tool-details.tsx +0 -52
  267. package/src/screens/tools/tool-info.tsx +0 -45
  268. package/src/screens/tools/tool-list.tsx +0 -106
  269. package/src/screens/tools/tool-schema.tsx +0 -44
  270. package/src/screens/tools/tool-test-details.tsx +0 -89
  271. package/src/screens/tools/tool-test-list.tsx +0 -73
  272. package/src/screens/variables/variable-details.tsx +0 -289
  273. package/src/screens/variables/variable-list.tsx +0 -110
  274. package/src/screens/welcome-modal.tsx +0 -294
  275. package/src/screens/workflows/workflow-details.tsx +0 -115
  276. package/src/screens/workflows/workflow-info.tsx +0 -22
  277. package/src/screens/workflows/workflow-instructions.tsx +0 -134
  278. package/src/screens/workflows/workflow-list.tsx +0 -117
  279. package/src/setupTests.ts +0 -38
  280. package/src/system-apps/assistants.app.ts +0 -7
  281. package/src/system-apps/console-logs.app.ts +0 -7
  282. package/src/system-apps/data-explorer.app.ts +0 -8
  283. package/src/system-apps/identity.app.ts +0 -9
  284. package/src/system-apps/index.ts +0 -77
  285. package/src/system-apps/mobile-settings.app.ts +0 -7
  286. package/src/system-apps/network-viewer.app.ts +0 -8
  287. package/src/system-apps/packages.app.ts +0 -7
  288. package/src/system-apps/search.app.ts +0 -8
  289. package/src/system-apps/settings.app.ts +0 -7
  290. package/src/system-apps/threads.app.ts +0 -7
  291. package/src/system-apps/tools.app.ts +0 -7
  292. package/src/system-apps/types.app.ts +0 -7
  293. package/src/system-apps/variables.app.ts +0 -7
  294. package/src/system-apps/workflows.app.ts +0 -7
  295. package/src/tabs-layout/network-status-indicator.test.tsx +0 -799
  296. package/src/tabs-layout/network-status-indicator.tsx +0 -756
  297. package/src/tabs-layout/tabs-layout.tsx +0 -857
  298. package/src/tabs-layout/tabs-state.test.ts +0 -152
  299. package/src/tabs-layout/tabs-state.ts +0 -587
  300. package/src/tabs-layout/use-network-status.test.tsx +0 -58
  301. package/src/tabs-layout/use-network-status.ts +0 -68
  302. package/src/ui-defaults/index.ts +0 -2
  303. package/src/ui-defaults/list-screen.tsx +0 -94
  304. package/src/ui-defaults/markdown-field.tsx +0 -87
  305. package/src/ui-router/routes-loader.ts +0 -193
  306. package/src/ui-router/ui-loader.test.tsx +0 -108
  307. package/src/ui-router/ui-loader.tsx +0 -606
  308. package/src/utils.ts +0 -95
  309. package/tsconfig.json +0 -18
@@ -1,4 +1,4 @@
1
- import { type INetworkControlConsumer, type INetworkDiagnosticsConsumer } from "@peers-app/peers-sdk";
1
+ import { type INetworkControlConsumer, type INetworkDiagnosticsConsumer, type ISyncCheckConsumer } from "@peers-app/peers-sdk";
2
2
  /** Electron-only capabilities intentionally excluded from portable network contracts. */
3
3
  export type NetworkViewerPlatformApi = {
4
4
  downloadRemoteDatabase?: (groupId: string, deviceId: string) => Promise<unknown>;
@@ -8,5 +8,7 @@ export type NetworkViewerPlatformApi = {
8
8
  export declare function getNetworkDiagnosticsConsumer(): Promise<INetworkDiagnosticsConsumer>;
9
9
  /** Return a typed control consumer bound to the current local host transport. */
10
10
  export declare function getNetworkControlConsumer(): Promise<INetworkControlConsumer>;
11
+ /** Return a typed System Sync Check consumer bound to the current local host transport. */
12
+ export declare function getSyncCheckConsumer(): Promise<ISyncCheckConsumer>;
11
13
  /** Return the context-isolated desktop-only capability bridge, when available. */
12
14
  export declare function getNetworkViewerPlatformApi(): NetworkViewerPlatformApi | undefined;
@@ -2,10 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getNetworkDiagnosticsConsumer = getNetworkDiagnosticsConsumer;
4
4
  exports.getNetworkControlConsumer = getNetworkControlConsumer;
5
+ exports.getSyncCheckConsumer = getSyncCheckConsumer;
5
6
  exports.getNetworkViewerPlatformApi = getNetworkViewerPlatformApi;
6
7
  const peers_sdk_1 = require("@peers-app/peers-sdk");
7
8
  const diagnostics = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getNetworkDiagnosticsContractDefinition)());
8
9
  const control = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getNetworkControlContractDefinition)());
10
+ const syncCheck = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getSyncCheckContractDefinition)());
9
11
  /** Return a typed diagnostics consumer bound to the current local host transport. */
10
12
  async function getNetworkDiagnosticsConsumer() {
11
13
  return diagnostics.get();
@@ -14,6 +16,10 @@ async function getNetworkDiagnosticsConsumer() {
14
16
  async function getNetworkControlConsumer() {
15
17
  return control.get();
16
18
  }
19
+ /** Return a typed System Sync Check consumer bound to the current local host transport. */
20
+ async function getSyncCheckConsumer() {
21
+ return syncCheck.get();
22
+ }
17
23
  /** Return the context-isolated desktop-only capability bridge, when available. */
18
24
  function getNetworkViewerPlatformApi() {
19
25
  return window.electronAPI?.networkViewer;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SyncRecoveryScreen = SyncRecoveryScreen;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
5
6
  const react_1 = require("react");
6
7
  const react_tiny_popover_1 = require("react-tiny-popover");
7
8
  const loading_indicator_1 = require("../../components/loading-indicator");
@@ -76,6 +77,107 @@ function RecoveryRows({ entry, busy, onRun, }) {
76
77
  : "This device has no row to keep", onClick: () => onRun(entry, "keepLocal"), children: "Keep this device's version" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-danger", disabled: busy, onClick: () => onRun(entry, "deleteEverywhere"), children: "Delete everywhere" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", disabled: busy, title: "Removes the entry on this device only. Other devices keep theirs.", onClick: () => onRun(entry, "ignoreLocal"), children: "Ignore on this device" })] }) })] }), (errorOpen || recordOpen) && ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsxs)("td", { colSpan: 7, className: "bg-body-tertiary", children: [errorOpen && ((0, jsx_runtime_1.jsx)("pre", { className: "small mb-0 font-monospace", style: detailsBlockStyle, children: formatError(entry.latestError) })), recordOpen && ((0, jsx_runtime_1.jsxs)("div", { className: errorOpen ? "mt-3" : undefined, children: [entry.changeCreatedAt != null && ((0, jsx_runtime_1.jsxs)("p", { className: "small mb-2", children: ["Change written ", formatWhen(entry.changeCreatedAt)] })), entry.conflictingRecordId && ((0, jsx_runtime_1.jsxs)("p", { className: "small mb-2", children: ["Another record holds the same unique value:", " ", (0, jsx_runtime_1.jsx)("code", { children: entry.conflictingRecordId })] })), entry.tombstoneRecordId &&
77
78
  entry.tombstoneRecordId === entry.conflictingRecordId && ((0, jsx_runtime_1.jsxs)("p", { className: "small mb-2", children: ["Keep deletes ", (0, jsx_runtime_1.jsx)("code", { children: entry.tombstoneRecordId }), " on every device."] })), entry.tombstoneRecordId && entry.tombstoneRecordId === entry.recordId && ((0, jsx_runtime_1.jsx)("p", { className: "small mb-2", children: "Keep snapshots the other record and tombstones this queued id." })), entry.keptRecordPreview && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("p", { className: "small mb-1", children: "This device's version" }), (0, jsx_runtime_1.jsx)("pre", { className: "small mb-2 font-monospace", style: detailsBlockStyle, children: entry.keptRecordPreview })] })), entry.queuedRecordPreview && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("p", { className: "small mb-1", children: "Queued change" }), (0, jsx_runtime_1.jsx)("pre", { className: "small mb-0 font-monospace", style: detailsBlockStyle, children: entry.queuedRecordPreview })] })), !entry.keptRecordPreview && !entry.queuedRecordPreview && entry.recordPreview && ((0, jsx_runtime_1.jsx)("pre", { className: "small mb-0 font-monospace", style: detailsBlockStyle, children: entry.recordPreview })), !entry.keptRecordPreview && !entry.queuedRecordPreview && !entry.recordPreview && ((0, jsx_runtime_1.jsx)("p", { className: "small text-muted mb-0", children: "This device has no stored copy of the record. The latest error may still quote the rejected value." }))] }))] }) }))] }));
78
79
  }
80
+ function MissingChangeRows({ gap }) {
81
+ const [open, setOpen] = (0, react_1.useState)(false);
82
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-link btn-sm p-0", "aria-label": open ? "Hide missing changes" : "Show missing changes", onClick: () => setOpen((current) => !current), children: open ? "Hide changes" : "Show changes" }), open && ((0, jsx_runtime_1.jsxs)("table", { className: "table table-sm small mb-0 mt-1", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table" }), (0, jsx_runtime_1.jsx)("th", { children: "Record" }), (0, jsx_runtime_1.jsx)("th", { children: "Change" }), (0, jsx_runtime_1.jsx)("th", { children: "Written" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: gap.missing.map((change) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: change.tableName }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { children: change.recordId }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsxs)("code", { title: change.changeId, children: [change.op, " ", change.path] }) }), (0, jsx_runtime_1.jsx)("td", { className: "text-nowrap", children: formatWhen(change.createdAt) })] }, change.changeId))) })] })), open && gap.missingCount > gap.missing.length && ((0, jsx_runtime_1.jsxs)("div", { className: "small text-muted", children: ["Showing ", gap.missing.length, " of ", gap.missingCount, "."] }))] }));
83
+ }
84
+ /** What the daily sync gap check found missing on this device, with Check now and Repair. */
85
+ function MissingChangesSection() {
86
+ const [gaps, setGaps] = (0, react_1.useState)([]);
87
+ const [availability, setAvailability] = (0, react_1.useState)("loading");
88
+ const loadGeneration = (0, react_1.useRef)(0);
89
+ const [busyKey, setBusyKey] = (0, react_1.useState)(null);
90
+ const [result, setResult] = (0, react_1.useState)(null);
91
+ const load = (0, react_1.useCallback)(async () => {
92
+ const generation = ++loadGeneration.current;
93
+ try {
94
+ const syncCheck = await (0, network_viewer_ipc_1.getSyncCheckConsumer)();
95
+ const { gaps } = await syncCheck.tools.getSyncGaps();
96
+ if (generation !== loadGeneration.current)
97
+ return;
98
+ setGaps(gaps);
99
+ setAvailability("ready");
100
+ }
101
+ catch (loadError) {
102
+ if (generation !== loadGeneration.current)
103
+ return;
104
+ setAvailability("unavailable");
105
+ setGaps([]);
106
+ setResult({
107
+ success: false,
108
+ message: loadError instanceof Error ? loadError.message : String(loadError),
109
+ });
110
+ }
111
+ }, []);
112
+ (0, react_1.useEffect)(() => {
113
+ let active = true;
114
+ let generation = 0;
115
+ let subscription;
116
+ const connect = () => {
117
+ const currentGeneration = ++generation;
118
+ subscription?.dispose();
119
+ subscription = undefined;
120
+ setAvailability("loading");
121
+ setGaps([]);
122
+ setResult(null);
123
+ void load();
124
+ void (0, network_viewer_ipc_1.getSyncCheckConsumer)()
125
+ .then((syncCheck) => {
126
+ if (active && generation === currentGeneration) {
127
+ subscription = syncCheck.observables?.status.subscribe(() => void load());
128
+ }
129
+ })
130
+ .catch(() => {
131
+ // `load` shows the unavailable state, including hosts without System Sync Check.
132
+ });
133
+ };
134
+ connect();
135
+ const unsubscribe = (0, peers_sdk_1.subscribeHostContractTransport)((transport) => {
136
+ if (transport)
137
+ connect();
138
+ else {
139
+ generation++;
140
+ loadGeneration.current++;
141
+ subscription?.dispose();
142
+ subscription = undefined;
143
+ setAvailability("unavailable");
144
+ setGaps([]);
145
+ }
146
+ }, { emitCurrent: false });
147
+ return () => {
148
+ active = false;
149
+ loadGeneration.current++;
150
+ subscription?.dispose();
151
+ unsubscribe();
152
+ };
153
+ }, [load]);
154
+ const run = async (key, action) => {
155
+ setBusyKey(key);
156
+ setResult(null);
157
+ try {
158
+ setResult({ success: true, message: (await action()).message });
159
+ }
160
+ catch (actionError) {
161
+ setResult({
162
+ success: false,
163
+ message: actionError instanceof Error ? actionError.message : String(actionError),
164
+ });
165
+ }
166
+ finally {
167
+ setBusyKey(null);
168
+ await load();
169
+ }
170
+ };
171
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "card mb-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header d-flex justify-content-between align-items-center", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["Missing changes", (0, jsx_runtime_1.jsxs)(InfoTip, { label: "About missing changes", children: ["Once a day this device compares its changes with each connected device in every group. Listed here are changes another device has that this device should have received but doesn't. ", (0, jsx_runtime_1.jsx)("strong", { children: "Repair" }), " fetches them from that device and applies them the same way sync does. If changes keep going missing, that points to a sync problem worth reporting."] })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-primary", disabled: busyKey !== null || availability !== "ready", onClick: () => void run("check", async () => (await (0, network_viewer_ipc_1.getSyncCheckConsumer)()).tools.checkSyncGaps()), children: busyKey === "check" ? "Checking…" : "Check now" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "card-body py-2", children: [result && ((0, jsx_runtime_1.jsx)("div", { className: `alert ${result.success ? "alert-info" : "alert-danger"} py-1 px-2 small mb-2`, children: result.message })), availability !== "ready" ? ((0, jsx_runtime_1.jsx)("div", { className: "small text-muted", children: availability === "loading"
172
+ ? "Loading missing change check…"
173
+ : "Missing change check is unavailable on this host." })) : gaps.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "small text-muted", children: "No missing changes found. Checks run once a day." })) : (gaps.map((gap) => {
174
+ const key = `${gap.dataContextId}:${gap.deviceId}`;
175
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "border-bottom py-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex justify-content-between align-items-start gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: gap.dataContextName }), ": ", gap.missingCount, gap.truncated ? "+" : "", " ", gap.missingCount === 1 ? "change" : "changes", " on", " ", (0, jsx_runtime_1.jsx)("span", { title: gap.deviceId, children: gap.deviceName || gap.deviceId }), " missing here", (0, jsx_runtime_1.jsxs)("div", { className: "small text-muted", children: ["Found ", formatWhen(gap.checkedAt)] })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-warning text-nowrap", disabled: busyKey !== null, onClick: () => void run(key, async () => (await (0, network_viewer_ipc_1.getSyncCheckConsumer)()).tools.repairSyncGaps({
176
+ dataContextId: gap.dataContextId,
177
+ deviceId: gap.deviceId,
178
+ })), children: busyKey === key ? "Repairing…" : "Repair" })] }), (0, jsx_runtime_1.jsx)(MissingChangeRows, { gap: gap })] }, key));
179
+ }))] })] }));
180
+ }
79
181
  /** Lists recovery-queue entries and the actions that resolve them. */
80
182
  function SyncRecoveryScreen({ onBack }) {
81
183
  const [entries, setEntries] = (0, react_1.useState)([]);
@@ -178,5 +280,5 @@ function SyncRecoveryScreen({ onBack }) {
178
280
  group.push(entry);
179
281
  groups.set(entry.dataContextId, group);
180
282
  }
181
- return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex justify-content-between align-items-center mb-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center gap-3", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", onClick: onBack, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-left me-1" }), "Back"] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h2", { className: "mb-1", children: "Sync recovery" }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small mb-0", children: "These records have change history, but this device could not write the row. Parked entries are not asked of other devices until you retry them." })] })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-primary", onClick: () => void load(), children: "Refresh" })] }), error && (0, jsx_runtime_1.jsx)("div", { className: "alert alert-danger py-2 small", children: error }), entries.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-success", children: "No records are waiting in the recovery queue." })) : ([...groups.entries()].map(([dataContextId, groupEntries]) => ((0, jsx_runtime_1.jsxs)("div", { className: "card mb-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "card-header", children: groupEntries[0]?.dataContextName || dataContextId }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm align-middle mb-0", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table" }), (0, jsx_runtime_1.jsx)("th", { children: "Record" }), (0, jsx_runtime_1.jsx)("th", { children: "First failed" }), (0, jsx_runtime_1.jsx)("th", { children: "Attempts" }), (0, jsx_runtime_1.jsx)("th", { children: "Status" }), (0, jsx_runtime_1.jsx)("th", { children: "Latest error" }), (0, jsx_runtime_1.jsxs)("th", { children: ["Actions", (0, jsx_runtime_1.jsx)(InfoTip, { label: "About these actions", children: (0, jsx_runtime_1.jsxs)("ul", { className: "mb-0 ps-3", children: [(0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Retry" }), " writes the record again from this device's history, then asks another device once. If a schema or unique-key entry is still queued after that, it is parked again. Use it after an upgrade that might accept a row this device rejected before."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Keep this device's version" }), " snapshots the row this device will keep. That is the queued row when it is already stored here, and otherwise the other row that holds the same unique value. When both rows are stored, the other record is deleted everywhere. When only the other row is stored, that row is kept and this queued id is tombstoned. Disabled when this device has no row to keep."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Delete everywhere" }), " deletes this record on every device, including devices that already have it."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Ignore on this device" }), " removes the entry here only. Other devices keep their own copy of the problem."] })] }) })] })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: groupEntries.map((entry) => ((0, jsx_runtime_1.jsx)(RecoveryRows, { entry: entry, busy: Boolean(busyKey?.startsWith(`${entry.dataContextId}:${entry.syncRecoveryId}:`)), onRun: (item, action) => void run(item, action) }, entry.syncRecoveryId))) })] }) })] }, dataContextId))))] }));
283
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex justify-content-between align-items-center mb-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center gap-3", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", onClick: onBack, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-left me-1" }), "Back"] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h2", { className: "mb-1", children: "Sync recovery" }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small mb-0", children: "These records have change history, but this device could not write the row. Parked entries are not asked of other devices until you retry them." })] })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-primary", onClick: () => void load(), children: "Refresh" })] }), error && (0, jsx_runtime_1.jsx)("div", { className: "alert alert-danger py-2 small", children: error }), (0, jsx_runtime_1.jsx)(MissingChangesSection, {}), entries.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-success", children: "No records are waiting in the recovery queue." })) : ([...groups.entries()].map(([dataContextId, groupEntries]) => ((0, jsx_runtime_1.jsxs)("div", { className: "card mb-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "card-header", children: groupEntries[0]?.dataContextName || dataContextId }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm align-middle mb-0", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table" }), (0, jsx_runtime_1.jsx)("th", { children: "Record" }), (0, jsx_runtime_1.jsx)("th", { children: "First failed" }), (0, jsx_runtime_1.jsx)("th", { children: "Attempts" }), (0, jsx_runtime_1.jsx)("th", { children: "Status" }), (0, jsx_runtime_1.jsx)("th", { children: "Latest error" }), (0, jsx_runtime_1.jsxs)("th", { children: ["Actions", (0, jsx_runtime_1.jsx)(InfoTip, { label: "About these actions", children: (0, jsx_runtime_1.jsxs)("ul", { className: "mb-0 ps-3", children: [(0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Retry" }), " writes the record again from this device's history, then asks another device once. If a schema or unique-key entry is still queued after that, it is parked again. Use it after an upgrade that might accept a row this device rejected before."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Keep this device's version" }), " snapshots the row this device will keep. That is the queued row when it is already stored here, and otherwise the other row that holds the same unique value. When both rows are stored, the other record is deleted everywhere. When only the other row is stored, that row is kept and this queued id is tombstoned. Disabled when this device has no row to keep."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Delete everywhere" }), " deletes this record on every device, including devices that already have it."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Ignore on this device" }), " removes the entry here only. Other devices keep their own copy of the problem."] })] }) })] })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: groupEntries.map((entry) => ((0, jsx_runtime_1.jsx)(RecoveryRows, { entry: entry, busy: Boolean(busyKey?.startsWith(`${entry.dataContextId}:${entry.syncRecoveryId}:`)), onRun: (item, action) => void run(item, action) }, entry.syncRecoveryId))) })] }) })] }, dataContextId))))] }));
182
284
  }
@@ -99,7 +99,8 @@ const CopyValueButton = (props) => {
99
99
  async function onCopy() {
100
100
  try {
101
101
  const userContext = await (0, peers_sdk_1.getUserContext)();
102
- const groupId = userContext.currentlyActiveGroupId();
102
+ // Personal can be "" here; leave it out so the provider uses the personal context.
103
+ const groupId = userContext.currentlyActiveGroupId() || undefined;
103
104
  const tools = await (0, host_shell_consumer_1.hostShellTools)();
104
105
  const result = await tools.copyVariableToClipboard({
105
106
  persistentVarId: pvar.persistentVarId,
@@ -37,13 +37,25 @@ function disconnectedLabelSuffix(disconnectedContextCount) {
37
37
  function connectedLabelSuffix(connectedContextCount) {
38
38
  return `; ${connectedContextCount} ${connectedContextCount === 1 ? "group" : "groups"} connected`;
39
39
  }
40
- function withStuckAttention(presentation, stuckRecoveryCount) {
41
- if (stuckRecoveryCount <= 0) {
40
+ function missingChangesLabel(missingChangeCount) {
41
+ return missingChangeCount === 1
42
+ ? "1 change missing from another device"
43
+ : `${missingChangeCount} changes missing from another device`;
44
+ }
45
+ function withStuckAttention(presentation, stuckRecoveryCount, missingChangeCount) {
46
+ if (stuckRecoveryCount <= 0 && missingChangeCount <= 0) {
42
47
  return presentation;
43
48
  }
44
- const attention = stuckRecoveryCount === 1
45
- ? "1 record needs attention"
46
- : `${stuckRecoveryCount} records need attention`;
49
+ const attention = [
50
+ stuckRecoveryCount === 1
51
+ ? "1 record needs attention"
52
+ : stuckRecoveryCount > 1
53
+ ? `${stuckRecoveryCount} records need attention`
54
+ : "",
55
+ missingChangeCount > 0 ? missingChangesLabel(missingChangeCount) : "",
56
+ ]
57
+ .filter(Boolean)
58
+ .join("; ");
47
59
  const labeled = { ...presentation, label: `${presentation.label}; ${attention}` };
48
60
  if (presentation.colorClass === "text-danger") {
49
61
  return labeled;
@@ -79,6 +91,7 @@ function getNetworkStatusPresentation(viewState, opts) {
79
91
  };
80
92
  }
81
93
  const { connectedContextCount, connectionCount, disconnectedContextCount, errorCount, state, stuckRecoveryCount, syncingContextCount, } = viewState.status;
94
+ const missingChangeCount = viewState.missingChangeCount ?? 0;
82
95
  const disconnectedOverlay = disconnectedContextCount > 0 && state !== "offline"
83
96
  ? { count: disconnectedContextCount }
84
97
  : undefined;
@@ -95,7 +108,7 @@ function getNetworkStatusPresentation(viewState, opts) {
95
108
  mark: "solid",
96
109
  disconnectedGroupsOverlay: disconnectedOverlay,
97
110
  connectedGroupsOverlay: groupsOverlay,
98
- }, stuckRecoveryCount);
111
+ }, stuckRecoveryCount, missingChangeCount);
99
112
  }
100
113
  if (state === "syncing" || opts?.holdSyncingVisual) {
101
114
  const longSync = state === "syncing" && opts?.syncingLongerThanGreenMs === true;
@@ -109,7 +122,7 @@ function getNetworkStatusPresentation(viewState, opts) {
109
122
  mark: longSync ? "indeterminate" : "pulse",
110
123
  disconnectedGroupsOverlay: disconnectedOverlay,
111
124
  connectedGroupsOverlay: groupsOverlay,
112
- }, stuckRecoveryCount);
125
+ }, stuckRecoveryCount, missingChangeCount);
113
126
  }
114
127
  if (state === "offline") {
115
128
  return withStuckAttention({
@@ -117,7 +130,7 @@ function getNetworkStatusPresentation(viewState, opts) {
117
130
  shortLabel: "Offline",
118
131
  iconClass: "bi-cloud-slash-fill",
119
132
  colorClass: "text-secondary",
120
- }, stuckRecoveryCount);
133
+ }, stuckRecoveryCount, missingChangeCount);
121
134
  }
122
135
  return withStuckAttention({
123
136
  label: `Network synced; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}${connectedSuffix}${disconnectedSuffix}`,
@@ -127,7 +140,7 @@ function getNetworkStatusPresentation(viewState, opts) {
127
140
  compactIcon: true,
128
141
  disconnectedGroupsOverlay: disconnectedOverlay,
129
142
  connectedGroupsOverlay: groupsOverlay,
130
- }, stuckRecoveryCount);
143
+ }, stuckRecoveryCount, missingChangeCount);
131
144
  }
132
145
  /**
133
146
  * Add the app-update badge and label to a network status presentation. Applies in every
@@ -233,10 +246,11 @@ function StatusCountRow({ label, value, className, }) {
233
246
  const tone = className ? ` ${className}` : "";
234
247
  return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("dt", { className: `col-7${tone}`, children: label }), (0, jsx_runtime_1.jsx)("dd", { className: `col-5 text-end${tone}`, children: value })] }));
235
248
  }
236
- function NetworkStatusDetails({ detailsId, status, appUpdate, unavailable, connectionsLoadState, onUseInvite, onOpenIdentity, onOpenViewer, onOpenRecovery, }) {
249
+ function NetworkStatusDetails({ detailsId, status, missingChangeCount, appUpdate, unavailable, connectionsLoadState, onUseInvite, onOpenIdentity, onOpenViewer, onOpenRecovery, }) {
250
+ const needsRecovery = !!status && (status.stuckRecoveryCount > 0 || missingChangeCount > 0);
237
251
  return ((0, jsx_runtime_1.jsx)("div", { id: detailsId, role: "dialog", "aria-label": "Network status details", className: "card shadow position-absolute end-0 mt-1", style: { top: "100%", width: "320px", zIndex: 1100 }, children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body p-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-2 mb-3", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-primary flex-fill", onClick: onUseInvite, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-qr-code me-1", "aria-hidden": "true" }), "Use Invite"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-primary flex-fill", onClick: onOpenIdentity, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-person-badge me-1", "aria-hidden": "true" }), "Identity"] })] }), (0, jsx_runtime_1.jsx)(app_update_controls_1.AppUpdatePopoverSection, { status: appUpdate }), (0, jsx_runtime_1.jsx)("h6", { className: "card-title mb-2", children: "Network status" }), unavailable || !status ? ((0, jsx_runtime_1.jsx)("p", { className: "small text-muted mb-3", children: "Network diagnostics are temporarily unavailable. The local host may still be starting or reconnecting." })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("dl", { className: "row small mb-2", children: [(0, jsx_runtime_1.jsx)(StatusCountRow, { label: "Connections", value: status.connectionCount }), (0, jsx_runtime_1.jsx)(StatusCountRow, { label: "Groups connected", value: status.connectedContextCount }), status.syncingContextCount > 0 && ((0, jsx_runtime_1.jsx)(StatusCountRow, { label: "Syncing contexts", value: status.syncingContextCount })), status.disconnectedContextCount > 0 && ((0, jsx_runtime_1.jsx)(StatusCountRow, { label: "Groups offline", value: status.disconnectedContextCount, className: "text-warning" })), status.errorCount > 0 && ((0, jsx_runtime_1.jsx)(StatusCountRow, { label: "Sync errors", value: status.errorCount })), status.lastSuccessfulSyncAt != null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Last sync" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end text-truncate", title: new Date(status.lastSuccessfulSyncAt).toLocaleString(), children: formatRelativeSyncTime(status.lastSuccessfulSyncAt) })] }))] }), status.errors.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "alert alert-danger py-2 px-2 small mb-2", children: [status.errors[0].message, status.errors.length > 1 && ` (+${status.errors.length - 1} more)`] })), status.stuckRecoveryCount > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning py-2 px-2 small mb-2", children: status.stuckRecoveryCount === 1
238
252
  ? "1 record needs attention"
239
- : `${status.stuckRecoveryCount} records need attention` })), (0, jsx_runtime_1.jsxs)("div", { className: "border-top pt-2 mt-1 mb-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "small text-muted mb-1", children: "Connected Devices" }), (0, jsx_runtime_1.jsx)(ConnectedDevicesList, { loadState: connectionsLoadState })] })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: `btn btn-sm btn-outline-primary w-100${status && status.stuckRecoveryCount > 0 ? " mb-2" : ""}`, onClick: onOpenViewer, children: "Open Network Viewer" }), status && status.stuckRecoveryCount > 0 && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-warning w-100", onClick: onOpenRecovery, children: "Sync recovery" }))] }) }));
253
+ : `${status.stuckRecoveryCount} records need attention` })), missingChangeCount > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning py-2 px-2 small mb-2", children: missingChangesLabel(missingChangeCount) })), (0, jsx_runtime_1.jsxs)("div", { className: "border-top pt-2 mt-1 mb-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "small text-muted mb-1", children: "Connected Devices" }), (0, jsx_runtime_1.jsx)(ConnectedDevicesList, { loadState: connectionsLoadState })] })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: `btn btn-sm btn-outline-primary w-100${needsRecovery ? " mb-2" : ""}`, onClick: onOpenViewer, children: "Open Network Viewer" }), needsRecovery && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-warning w-100", onClick: onOpenRecovery, children: "Sync recovery" }))] }) }));
240
254
  }
241
255
  /** Accessible reactive network status button and details popover. */
242
256
  function NetworkStatusIndicator({ compact = false }) {
@@ -363,7 +377,7 @@ function NetworkStatusIndicator({ compact = false }) {
363
377
  .network-status-update-badge.bi::before {
364
378
  vertical-align: 0;
365
379
  }
366
- ` })] }))] }), !compact && ((0, jsx_runtime_1.jsxs)("span", { className: "small", children: [viewState.status?.connectionCount ?? 0, (0, jsx_runtime_1.jsx)("span", { className: "visually-hidden", children: " connections" })] }))] }), open && ((0, jsx_runtime_1.jsx)(NetworkStatusDetails, { detailsId: detailsId, status: viewState.status, appUpdate: appUpdate, unavailable: viewState.availability !== "ready", connectionsLoadState: connectionsLoadState, onUseInvite: () => {
380
+ ` })] }))] }), !compact && ((0, jsx_runtime_1.jsxs)("span", { className: "small", children: [viewState.status?.connectionCount ?? 0, (0, jsx_runtime_1.jsx)("span", { className: "visually-hidden", children: " connections" })] }))] }), open && ((0, jsx_runtime_1.jsx)(NetworkStatusDetails, { detailsId: detailsId, status: viewState.status, missingChangeCount: viewState.missingChangeCount ?? 0, appUpdate: appUpdate, unavailable: viewState.availability !== "ready", connectionsLoadState: connectionsLoadState, onUseInvite: () => {
367
381
  setOpen(false);
368
382
  (0, tabs_state_1.goToTabPath)(identity_routes_1.IDENTITY_ACCEPT_PATH);
369
383
  }, onOpenIdentity: () => {
@@ -3,6 +3,11 @@ import { type INetworkStatusSummary } from "@peers-app/peers-sdk";
3
3
  export interface INetworkStatusViewState {
4
4
  availability: "loading" | "ready" | "unavailable";
5
5
  status?: INetworkStatusSummary;
6
+ /** Changes the daily sync gap check found on another device but not on this one. */
7
+ missingChangeCount?: number;
6
8
  }
7
- /** Subscribe to the current host's System Network Diagnostics status observable. */
9
+ /**
10
+ * Subscribe to the current host's System Network Diagnostics status observable, and to System
11
+ * Sync Check's missing-change count.
12
+ */
8
13
  export declare function useNetworkStatus(): INetworkStatusViewState;
@@ -4,7 +4,10 @@ exports.useNetworkStatus = useNetworkStatus;
4
4
  const peers_sdk_1 = require("@peers-app/peers-sdk");
5
5
  const react_1 = require("react");
6
6
  const network_viewer_ipc_1 = require("../screens/network-viewer/network-viewer-ipc");
7
- /** Subscribe to the current host's System Network Diagnostics status observable. */
7
+ /**
8
+ * Subscribe to the current host's System Network Diagnostics status observable, and to System
9
+ * Sync Check's missing-change count.
10
+ */
8
11
  function useNetworkStatus() {
9
12
  const [viewState, setViewState] = (0, react_1.useState)({
10
13
  availability: "loading",
@@ -13,12 +16,15 @@ function useNetworkStatus() {
13
16
  let active = true;
14
17
  let generation = 0;
15
18
  let statusSubscription;
19
+ let syncCheckSubscription;
16
20
  let hadTransport = false;
17
21
  const unsubscribeTransport = (0, peers_sdk_1.subscribeHostContractTransport)((transport) => {
18
22
  generation += 1;
19
23
  const currentGeneration = generation;
20
24
  statusSubscription?.dispose();
21
25
  statusSubscription = undefined;
26
+ syncCheckSubscription?.dispose();
27
+ syncCheckSubscription = undefined;
22
28
  if (!transport) {
23
29
  setViewState({ availability: hadTransport ? "unavailable" : "loading" });
24
30
  return;
@@ -30,13 +36,14 @@ function useNetworkStatus() {
30
36
  if (!active || generation !== currentGeneration) {
31
37
  return;
32
38
  }
33
- setViewState({
39
+ setViewState((current) => ({
40
+ ...current,
34
41
  availability: "ready",
35
42
  status: diagnostics.observables.status(),
36
- });
43
+ }));
37
44
  statusSubscription = diagnostics.observables.status.subscribe((status) => {
38
45
  if (active && generation === currentGeneration) {
39
- setViewState({ availability: "ready", status });
46
+ setViewState((current) => ({ ...current, availability: "ready", status }));
40
47
  }
41
48
  });
42
49
  })
@@ -45,11 +52,27 @@ function useNetworkStatus() {
45
52
  setViewState({ availability: "unavailable" });
46
53
  }
47
54
  });
55
+ // A missing count only adds a warning; without it the indicator still works.
56
+ void (0, network_viewer_ipc_1.getSyncCheckConsumer)()
57
+ .then((syncCheck) => {
58
+ if (!active || generation !== currentGeneration) {
59
+ return;
60
+ }
61
+ const update = ({ missingChangeCount }) => {
62
+ if (active && generation === currentGeneration) {
63
+ setViewState((current) => ({ ...current, missingChangeCount }));
64
+ }
65
+ };
66
+ update(syncCheck.observables.status());
67
+ syncCheckSubscription = syncCheck.observables.status.subscribe(update);
68
+ })
69
+ .catch(() => { });
48
70
  });
49
71
  return () => {
50
72
  active = false;
51
73
  generation += 1;
52
74
  statusSubscription?.dispose();
75
+ syncCheckSubscription?.dispose();
53
76
  unsubscribeTransport();
54
77
  };
55
78
  }, []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-ui",
3
- "version": "0.27.2",
3
+ "version": "0.27.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-ui.git"
@@ -9,6 +9,12 @@
9
9
  "license": "MIT",
10
10
  "main": "dist/index.js",
11
11
  "types": "dist/index.d.ts",
12
+ "files": [
13
+ "dist",
14
+ "!dist/**/*.test.*",
15
+ "!dist/**/__tests__",
16
+ "README.md"
17
+ ],
12
18
  "publishConfig": {
13
19
  "access": "public"
14
20
  },
@@ -30,7 +36,7 @@
30
36
  "lint:fix": "biome check --write ."
31
37
  },
32
38
  "peerDependencies": {
33
- "@peers-app/peers-sdk": "^0.27.2",
39
+ "@peers-app/peers-sdk": "^0.27.4",
34
40
  "bootstrap": "^5.3.3",
35
41
  "react": "^18.0.0",
36
42
  "react-dom": "^18.0.0"
@@ -41,7 +47,7 @@
41
47
  "@babel/preset-react": "^7.24.1",
42
48
  "@babel/preset-typescript": "^7.27.1",
43
49
  "@biomejs/biome": "2.3.11",
44
- "@peers-app/peers-sdk": "0.27.2",
50
+ "@peers-app/peers-sdk": "0.27.4",
45
51
  "@testing-library/dom": "^10.4.0",
46
52
  "@testing-library/jest-dom": "^6.6.3",
47
53
  "@testing-library/react": "^16.3.0",
@@ -1,34 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(mkdir:*)",
5
- "Bash(yarn add:*)",
6
- "Bash(yarn test:*)",
7
- "Bash(npm test)",
8
- "Bash(yarn build)",
9
- "Bash(yarn jest:*)",
10
- "Bash(mv:*)",
11
- "Bash(npm run build:*)",
12
- "Bash(cd:*)",
13
- "Bash(npm test:*)",
14
- "Bash(npm run test:*)",
15
- "Bash(yarn tsc:*)",
16
- "WebFetch(domain:developer.mozilla.org)",
17
- "Read(/Users/mark.archer/peers-device/src/**)",
18
- "Read(/Users/mark.archer/peers-packages/tasks/**)",
19
- "Read(/Users/mark.archer/peers-packages/tasks/**)",
20
- "Read(/Users/mark.archer/peers-packages/tasks/**)",
21
- "Read(/Users/mark.archer/peers-packages/tasks/**)",
22
- "Read(/Users/mark.archer/peers-packages/tasks/src/**)",
23
- "Read(/Users/mark.archer/peers-packages/tasks/dist/**)",
24
- "Read(/Users/mark.archer/peers-packages/tasks/dist/**)",
25
- "Read(/Users/mark.archer/peers-packages/tasks/dist/**)",
26
- "Read(/Users/mark.archer/peers-packages/tasks/src/**)",
27
- "Read(/Users/mark.archer/peers-packages/tasks/src/**)"
28
- ],
29
- "deny": [],
30
- "additionalDirectories": [
31
- "/Users/mark.archer/peers-packages/tasks/dist"
32
- ]
33
- }
34
- }
package/.github/README.md DELETED
@@ -1,52 +0,0 @@
1
- # GitHub Actions for @peers-app/peers-ui
2
-
3
- ## Quick Start
4
-
5
- ### 1. Configure npm Token
6
-
7
- Add your npm token as a GitHub Secret:
8
-
9
- 1. Go to **Settings** → **Secrets and variables** → **Actions**
10
- 2. Click **New repository secret**
11
- 3. Name: `NPM_TOKEN`
12
- 4. Value: Your npm automation token
13
- 5. Click **Add secret**
14
-
15
- ### 2. Create npm Token
16
-
17
- If you don't have one:
18
-
19
- 1. Go to [npmjs.com](https://www.npmjs.com)
20
- 2. **Account Settings** → **Access Tokens**
21
- 3. **Generate New Token** → **Automation**
22
- 4. Copy the token and add it to GitHub Secrets
23
-
24
- ### 3. Release
25
-
26
- **IMPORTANT**: Ensure @peers-app/peers-sdk is published first!
27
-
28
- ```bash
29
- # Update @peers-app/peers-sdk dependency if needed
30
- # Edit package.json: "@peers-app/peers-sdk": "^0.1.1"
31
-
32
- # Update version
33
- npm version patch # 0.0.14 → 0.0.15
34
-
35
- # Push tag (triggers CI/CD)
36
- git push origin main
37
- git push origin --tags
38
- ```
39
-
40
- ## Workflow
41
-
42
- The `publish.yml` workflow:
43
- - Triggers on version tags (`v*.*.*`)
44
- - Runs tests
45
- - Builds the package
46
- - Publishes to npm
47
- - Creates GitHub release
48
-
49
- ## Dependencies
50
-
51
- This package depends on:
52
- - **@peers-app/peers-sdk**: Ensure it's published before releasing
@@ -1,49 +0,0 @@
1
- name: Cleanup old artifacts
2
- on:
3
- workflow_dispatch: # manual trigger
4
- schedule:
5
- - cron: '0 0 * * 0' # every Sunday at midnight UTC
6
-
7
- jobs:
8
- cleanup:
9
- runs-on: ubuntu-latest
10
- permissions:
11
- actions: write # needed to delete artifacts
12
- contents: read
13
- steps:
14
- - name: Install jq
15
- run: sudo apt-get install -y jq
16
-
17
- - name: Log in to GitHub CLI
18
- run: gh auth setup-git
19
- env:
20
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21
-
22
- - name: List and delete artifacts
23
- env:
24
- REPO: ${{ github.repository }}
25
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26
- run: |
27
- echo "Fetching all artifacts for $REPO..."
28
- gh api repos/$REPO/actions/artifacts --paginate > artifacts.json
29
-
30
- count=$(jq '.artifacts | length' artifacts.json)
31
- echo "Found $count artifacts."
32
- echo
33
-
34
- jq -c '.artifacts[]' artifacts.json | while read -r artifact; do
35
- id=$(echo "$artifact" | jq -r '.id')
36
- name=$(echo "$artifact" | jq -r '.name')
37
- size=$(echo "$artifact" | jq -r '.size_in_bytes')
38
- created=$(echo "$artifact" | jq -r '.created_at')
39
- expired=$(echo "$artifact" | jq -r '.expired')
40
-
41
- size_mb=$(echo "scale=2; $size/1024/1024" | bc)
42
- echo "🗑️ Deleting artifact: \"$name\" ($size_mb MB, created $created, expired=$expired)"
43
-
44
- gh api --method DELETE repos/$REPO/actions/artifacts/$id || \
45
- echo "⚠️ Failed to delete artifact ID $id"
46
- done
47
-
48
- echo
49
- echo "✅ Cleanup complete!"
@@ -1,51 +0,0 @@
1
- name: Publish to npm
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*.*.*'
7
- - '*.*.*'
8
- workflow_dispatch:
9
-
10
- jobs:
11
- publish:
12
- runs-on: ubuntu-latest
13
-
14
- steps:
15
- - name: Checkout code
16
- uses: actions/checkout@v6
17
-
18
- - name: Enforce release freeze
19
- run: node release-guard.js
20
-
21
- - name: Setup Node.js
22
- uses: actions/setup-node@v6
23
- with:
24
- node-version: '22'
25
- registry-url: 'https://registry.npmjs.org'
26
- cache: 'npm'
27
-
28
- - name: Install dependencies
29
- run: npm ci
30
-
31
- - name: Lint (Biome)
32
- run: npm run lint
33
-
34
- # - name: Run tests
35
- # run: npm test
36
-
37
- - name: Build package
38
- run: npm run build
39
-
40
- - name: Publish to npm
41
- run: npm publish
42
- env:
43
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
44
-
45
- - name: Create GitHub Release
46
- uses: softprops/action-gh-release@v3
47
- if: startsWith(github.ref, 'refs/tags/')
48
- with:
49
- generate_release_notes: true
50
- env:
51
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/babel.config.js DELETED
@@ -1,7 +0,0 @@
1
- module.exports = {
2
- presets: [
3
- ["@babel/preset-env", { targets: { node: "current" } }],
4
- ["@babel/preset-react", { runtime: "automatic" }],
5
- "@babel/preset-typescript",
6
- ],
7
- };