@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,461 +0,0 @@
1
- import type { INetworkSyncRecoveryEntry, SyncRecoveryResolution } from "@peers-app/peers-sdk";
2
- import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
3
- import { Popover } from "react-tiny-popover";
4
- import { LoadingIndicator } from "../../components/loading-indicator";
5
- import { getNetworkControlConsumer, getNetworkDiagnosticsConsumer } from "./network-viewer-ipc";
6
-
7
- /** Props for {@link SyncRecoveryScreen}. */
8
- interface ISyncRecoveryScreenProps {
9
- /** Return to the Network Viewer overview. */
10
- onBack: () => void;
11
- }
12
-
13
- function formatWhen(timestamp: number): string {
14
- return new Date(timestamp).toLocaleString();
15
- }
16
-
17
- function statusLabel(entry: INetworkSyncRecoveryEntry): string {
18
- if (entry.status === "retrying") {
19
- return "Retrying";
20
- }
21
- if (entry.parkedReason === "schema") {
22
- return "Parked, schema";
23
- }
24
- if (entry.parkedReason === "unique-conflict") {
25
- return "Parked, conflict";
26
- }
27
- if (entry.parkedReason === "unresolvable") {
28
- return "Parked, needs a decision";
29
- }
30
- return "Parked";
31
- }
32
-
33
- function statusExplanation(entry: INetworkSyncRecoveryEntry): string {
34
- if (entry.status === "retrying") {
35
- return "This device is still asking others for a copy it can write. The wait starts at one minute and doubles after each try, up to one hour. It is marked as needing attention after 5 tries spanning at least 10 minutes.";
36
- }
37
- if (entry.parkedReason === "schema") {
38
- return "The saved change does not match this device's rules for the table — a missing field, an empty id, or an id in the wrong format. Every device has the same change, so asking them again will not help. It is tried again from history once each time this device starts, when a newer change arrives, and when you press Retry. After an upgrade that accepts the row, the next startup or Retry can write it.";
39
- }
40
- if (entry.parkedReason === "unique-conflict") {
41
- return "Two records want the same unique value, such as one variable name. This table has no automatic way to choose a winner, so nothing is deleted until you pick an action.";
42
- }
43
- if (entry.parkedReason === "unresolvable") {
44
- return "An automatic fix could not be applied safely — the other record was missing, more than one row matched, or the write failed. The rows were left as they are. Choose an action when you know which version should remain.";
45
- }
46
- return "This entry is waiting and is not being sent to other devices. Retry tries it again. The other actions clear it.";
47
- }
48
-
49
- /** Show a validation payload as indented JSON when the error ends with one. */
50
- function formatError(error: string): string {
51
- const marker = error.search(/[[{]/);
52
- if (marker < 0) {
53
- return error;
54
- }
55
- const head = error.slice(0, marker).trimEnd();
56
- try {
57
- return `${head}\n${JSON.stringify(JSON.parse(error.slice(marker)), null, 2)}`;
58
- } catch {
59
- return error;
60
- }
61
- }
62
-
63
- const detailsBlockStyle = {
64
- maxHeight: "16.8em",
65
- lineHeight: 1.4,
66
- overflow: "auto",
67
- whiteSpace: "pre-wrap",
68
- wordBreak: "break-word",
69
- } as const;
70
-
71
- function errorSummary(error: string): string {
72
- const line = error.split("\n")[0] ?? error;
73
- return line.length > 80 ? `${line.slice(0, 77)}…` : line;
74
- }
75
-
76
- function InfoTip({ label, children }: { label: string; children: ReactNode }) {
77
- const [open, setOpen] = useState(false);
78
- return (
79
- <Popover
80
- isOpen={open}
81
- positions={["bottom", "top", "left", "right"]}
82
- align="start"
83
- onClickOutside={() => setOpen(false)}
84
- content={() => (
85
- <div
86
- className="card p-2 shadow small"
87
- style={{ maxWidth: 420, zIndex: 1000001, textAlign: "left" }}
88
- >
89
- {children}
90
- </div>
91
- )}
92
- >
93
- <button
94
- type="button"
95
- className="btn btn-link btn-sm p-0 ms-1 align-baseline text-info"
96
- aria-label={label}
97
- onClick={() => setOpen((current) => !current)}
98
- >
99
- <i className="bi bi-info-circle" />
100
- </button>
101
- </Popover>
102
- );
103
- }
104
-
105
- function RecoveryRows({
106
- entry,
107
- busy,
108
- onRun,
109
- }: {
110
- entry: INetworkSyncRecoveryEntry;
111
- busy: boolean;
112
- onRun: (entry: INetworkSyncRecoveryEntry, action: "retry" | SyncRecoveryResolution) => void;
113
- }) {
114
- const [recordOpen, setRecordOpen] = useState(false);
115
- const [errorOpen, setErrorOpen] = useState(false);
116
- return (
117
- <>
118
- <tr>
119
- <td>{entry.tableName}</td>
120
- <td>
121
- <code title={entry.recordId}>{entry.recordId}</code>
122
- {entry.recordSummary && (
123
- <div className="small text-muted" style={{ maxWidth: 220 }}>
124
- {entry.recordSummary}
125
- </div>
126
- )}
127
- <button
128
- type="button"
129
- className="btn btn-link btn-sm p-0"
130
- aria-label={recordOpen ? "Hide record details" : "Show record details"}
131
- onClick={() => setRecordOpen((current) => !current)}
132
- >
133
- {recordOpen ? "Hide details" : "Details"}
134
- </button>
135
- </td>
136
- <td className="text-nowrap small">{formatWhen(entry.firstFailedAt)}</td>
137
- <td>{entry.attemptCount}</td>
138
- <td className="text-nowrap">
139
- {statusLabel(entry)}
140
- <InfoTip label={`About status ${statusLabel(entry)}`}>{statusExplanation(entry)}</InfoTip>
141
- </td>
142
- <td>
143
- <div className="small text-muted" style={{ maxWidth: 220 }}>
144
- {errorSummary(entry.latestError)}
145
- </div>
146
- <button
147
- type="button"
148
- className="btn btn-link btn-sm p-0"
149
- aria-label={errorOpen ? "Hide error details" : "Show error details"}
150
- onClick={() => setErrorOpen((current) => !current)}
151
- >
152
- {errorOpen ? "Hide details" : "Details"}
153
- </button>
154
- </td>
155
- <td>
156
- <div className="d-flex flex-column gap-1">
157
- <button
158
- type="button"
159
- className="btn btn-sm btn-outline-primary"
160
- disabled={busy}
161
- onClick={() => onRun(entry, "retry")}
162
- >
163
- Retry
164
- </button>
165
- <button
166
- type="button"
167
- className="btn btn-sm btn-outline-primary"
168
- disabled={busy || !entry.localRowExists}
169
- title={
170
- entry.localRowExists
171
- ? "Write this device's row as a snapshot every device will adopt"
172
- : "This device has no row to keep"
173
- }
174
- onClick={() => onRun(entry, "keepLocal")}
175
- >
176
- Keep this device's version
177
- </button>
178
- <button
179
- type="button"
180
- className="btn btn-sm btn-outline-danger"
181
- disabled={busy}
182
- onClick={() => onRun(entry, "deleteEverywhere")}
183
- >
184
- Delete everywhere
185
- </button>
186
- <button
187
- type="button"
188
- className="btn btn-sm btn-outline-secondary"
189
- disabled={busy}
190
- title="Removes the entry on this device only. Other devices keep theirs."
191
- onClick={() => onRun(entry, "ignoreLocal")}
192
- >
193
- Ignore on this device
194
- </button>
195
- </div>
196
- </td>
197
- </tr>
198
- {(errorOpen || recordOpen) && (
199
- <tr>
200
- <td colSpan={7} className="bg-body-tertiary">
201
- {errorOpen && (
202
- <pre className="small mb-0 font-monospace" style={detailsBlockStyle}>
203
- {formatError(entry.latestError)}
204
- </pre>
205
- )}
206
- {recordOpen && (
207
- <div className={errorOpen ? "mt-3" : undefined}>
208
- {entry.changeCreatedAt != null && (
209
- <p className="small mb-2">Change written {formatWhen(entry.changeCreatedAt)}</p>
210
- )}
211
- {entry.conflictingRecordId && (
212
- <p className="small mb-2">
213
- Another record holds the same unique value:{" "}
214
- <code>{entry.conflictingRecordId}</code>
215
- </p>
216
- )}
217
- {entry.tombstoneRecordId &&
218
- entry.tombstoneRecordId === entry.conflictingRecordId && (
219
- <p className="small mb-2">
220
- Keep deletes <code>{entry.tombstoneRecordId}</code> on every device.
221
- </p>
222
- )}
223
- {entry.tombstoneRecordId && entry.tombstoneRecordId === entry.recordId && (
224
- <p className="small mb-2">
225
- Keep snapshots the other record and tombstones this queued id.
226
- </p>
227
- )}
228
- {entry.keptRecordPreview && (
229
- <>
230
- <p className="small mb-1">This device's version</p>
231
- <pre className="small mb-2 font-monospace" style={detailsBlockStyle}>
232
- {entry.keptRecordPreview}
233
- </pre>
234
- </>
235
- )}
236
- {entry.queuedRecordPreview && (
237
- <>
238
- <p className="small mb-1">Queued change</p>
239
- <pre className="small mb-0 font-monospace" style={detailsBlockStyle}>
240
- {entry.queuedRecordPreview}
241
- </pre>
242
- </>
243
- )}
244
- {!entry.keptRecordPreview && !entry.queuedRecordPreview && entry.recordPreview && (
245
- <pre className="small mb-0 font-monospace" style={detailsBlockStyle}>
246
- {entry.recordPreview}
247
- </pre>
248
- )}
249
- {!entry.keptRecordPreview && !entry.queuedRecordPreview && !entry.recordPreview && (
250
- <p className="small text-muted mb-0">
251
- This device has no stored copy of the record. The latest error may still quote
252
- the rejected value.
253
- </p>
254
- )}
255
- </div>
256
- )}
257
- </td>
258
- </tr>
259
- )}
260
- </>
261
- );
262
- }
263
-
264
- /** Lists recovery-queue entries and the actions that resolve them. */
265
- export function SyncRecoveryScreen({ onBack }: ISyncRecoveryScreenProps) {
266
- const [entries, setEntries] = useState<INetworkSyncRecoveryEntry[]>([]);
267
- const [loading, setLoading] = useState(true);
268
- const [busyKey, setBusyKey] = useState<string | null>(null);
269
- const [error, setError] = useState<string | null>(null);
270
- const loadGeneration = useRef(0);
271
-
272
- const load = useCallback(async (preserveError = false) => {
273
- const generation = ++loadGeneration.current;
274
- try {
275
- const diagnostics = await getNetworkDiagnosticsConsumer();
276
- const result = await diagnostics.tools.getSyncRecoveryEntries();
277
- if (generation !== loadGeneration.current) {
278
- return;
279
- }
280
- setEntries(result.entries);
281
- if (!preserveError) {
282
- setError(null);
283
- }
284
- } catch (loadError) {
285
- if (generation !== loadGeneration.current) {
286
- return;
287
- }
288
- setError(loadError instanceof Error ? loadError.message : String(loadError));
289
- } finally {
290
- if (generation === loadGeneration.current) {
291
- setLoading(false);
292
- }
293
- }
294
- }, []);
295
-
296
- useEffect(() => {
297
- void load();
298
- }, [load]);
299
-
300
- useEffect(() => {
301
- let active = true;
302
- let subscription: { dispose(): void } | undefined;
303
- void getNetworkDiagnosticsConsumer().then((diagnostics) => {
304
- if (!active) {
305
- return;
306
- }
307
- subscription = diagnostics.observables?.status.subscribe(() => {
308
- void load();
309
- });
310
- });
311
- return () => {
312
- active = false;
313
- subscription?.dispose();
314
- };
315
- }, [load]);
316
-
317
- const run = async (
318
- entry: INetworkSyncRecoveryEntry,
319
- action: "retry" | SyncRecoveryResolution,
320
- ) => {
321
- if (action === "deleteEverywhere") {
322
- const confirmed = window.confirm(
323
- `Delete ${entry.tableName}/${entry.recordId} on every device? This writes a synced delete.`,
324
- );
325
- if (!confirmed) {
326
- return;
327
- }
328
- }
329
- const key = `${entry.dataContextId}:${entry.syncRecoveryId}:${action}`;
330
- setBusyKey(key);
331
- setError(null);
332
- try {
333
- const control = await getNetworkControlConsumer();
334
- const result =
335
- action === "retry"
336
- ? await control.tools.retrySyncRecovery({
337
- dataContextId: entry.dataContextId,
338
- tableName: entry.tableName,
339
- recordId: entry.recordId,
340
- syncRecoveryId: entry.syncRecoveryId,
341
- })
342
- : await control.tools.resolveSyncRecovery({
343
- dataContextId: entry.dataContextId,
344
- tableName: entry.tableName,
345
- recordId: entry.recordId,
346
- resolution: action,
347
- syncRecoveryId: entry.syncRecoveryId,
348
- });
349
- if (!result.success) {
350
- setError(result.message);
351
- await load(true);
352
- } else {
353
- setError(null);
354
- await load();
355
- }
356
- } catch (actionError) {
357
- setError(actionError instanceof Error ? actionError.message : String(actionError));
358
- } finally {
359
- setBusyKey(null);
360
- }
361
- };
362
-
363
- if (loading) {
364
- return <LoadingIndicator />;
365
- }
366
-
367
- const groups = new Map<string, INetworkSyncRecoveryEntry[]>();
368
- for (const entry of entries) {
369
- const group = groups.get(entry.dataContextId) ?? [];
370
- group.push(entry);
371
- groups.set(entry.dataContextId, group);
372
- }
373
-
374
- return (
375
- <div className="container-fluid p-4">
376
- <div className="d-flex justify-content-between align-items-center mb-3">
377
- <div className="d-flex align-items-center gap-3">
378
- <button type="button" className="btn btn-sm btn-outline-secondary" onClick={onBack}>
379
- <i className="bi bi-arrow-left me-1" />
380
- Back
381
- </button>
382
- <div>
383
- <h2 className="mb-1">Sync recovery</h2>
384
- <p className="text-muted small mb-0">
385
- These records have change history, but this device could not write the row. Parked
386
- entries are not asked of other devices until you retry them.
387
- </p>
388
- </div>
389
- </div>
390
- <button type="button" className="btn btn-sm btn-primary" onClick={() => void load()}>
391
- Refresh
392
- </button>
393
- </div>
394
- {error && <div className="alert alert-danger py-2 small">{error}</div>}
395
- {entries.length === 0 ? (
396
- <div className="alert alert-success">No records are waiting in the recovery queue.</div>
397
- ) : (
398
- [...groups.entries()].map(([dataContextId, groupEntries]) => (
399
- <div key={dataContextId} className="card mb-3">
400
- <div className="card-header">{groupEntries[0]?.dataContextName || dataContextId}</div>
401
- <div className="table-responsive">
402
- <table className="table table-sm align-middle mb-0">
403
- <thead>
404
- <tr>
405
- <th>Table</th>
406
- <th>Record</th>
407
- <th>First failed</th>
408
- <th>Attempts</th>
409
- <th>Status</th>
410
- <th>Latest error</th>
411
- <th>
412
- Actions
413
- <InfoTip label="About these actions">
414
- <ul className="mb-0 ps-3">
415
- <li>
416
- <strong>Retry</strong> writes the record again from this device's
417
- history, then asks another device once. If a schema or unique-key entry
418
- is still queued after that, it is parked again. Use it after an upgrade
419
- that might accept a row this device rejected before.
420
- </li>
421
- <li>
422
- <strong>Keep this device's version</strong> snapshots the row this
423
- device will keep. That is the queued row when it is already stored here,
424
- and otherwise the other row that holds the same unique value. When both
425
- rows are stored, the other record is deleted everywhere. When only the
426
- other row is stored, that row is kept and this queued id is tombstoned.
427
- Disabled when this device has no row to keep.
428
- </li>
429
- <li>
430
- <strong>Delete everywhere</strong> deletes this record on every device,
431
- including devices that already have it.
432
- </li>
433
- <li>
434
- <strong>Ignore on this device</strong> removes the entry here only.
435
- Other devices keep their own copy of the problem.
436
- </li>
437
- </ul>
438
- </InfoTip>
439
- </th>
440
- </tr>
441
- </thead>
442
- <tbody>
443
- {groupEntries.map((entry) => (
444
- <RecoveryRows
445
- key={entry.syncRecoveryId}
446
- entry={entry}
447
- busy={Boolean(
448
- busyKey?.startsWith(`${entry.dataContextId}:${entry.syncRecoveryId}:`),
449
- )}
450
- onRun={(item, action) => void run(item, action)}
451
- />
452
- ))}
453
- </tbody>
454
- </table>
455
- </div>
456
- </div>
457
- ))
458
- )}
459
- </div>
460
- );
461
- }