@plures/design-dojo 0.5.2 → 0.7.1

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 (250) hide show
  1. package/dist/app/CanvasBreadcrumb.svelte +146 -0
  2. package/dist/app/CanvasBreadcrumb.svelte.d.ts +19 -0
  3. package/dist/app/CanvasBreadcrumb.types.js +1 -0
  4. package/dist/app/ChatInput.svelte +296 -0
  5. package/dist/app/ChatInput.svelte.d.ts +15 -0
  6. package/dist/app/ChatView.svelte +542 -0
  7. package/dist/app/ChatView.svelte.d.ts +19 -0
  8. package/dist/app/ChatView.types.js +1 -0
  9. package/dist/app/ConversationGraph.svelte +471 -0
  10. package/dist/app/ConversationGraph.svelte.d.ts +20 -0
  11. package/dist/app/FirstRunWizard.svelte +542 -0
  12. package/dist/app/FirstRunWizard.svelte.d.ts +10 -0
  13. package/dist/app/FirstRunWizard.types.js +1 -0
  14. package/dist/app/MemorySidebar.svelte +258 -0
  15. package/dist/app/MemorySidebar.svelte.d.ts +9 -0
  16. package/dist/app/MemorySidebar.types.js +1 -0
  17. package/dist/app/PeerStatusPanel.svelte +464 -0
  18. package/dist/app/PeerStatusPanel.svelte.d.ts +16 -0
  19. package/dist/app/ProcedureCanvas.svelte +994 -0
  20. package/dist/app/ProcedureCanvas.svelte.d.ts +12 -0
  21. package/dist/app/ProcedureCanvas.types.js +1 -0
  22. package/dist/app/ProcedureEditor.svelte +494 -0
  23. package/dist/app/ProcedureEditor.svelte.d.ts +11 -0
  24. package/dist/app/ProcedureEditor.types.js +1 -0
  25. package/dist/app/ProcedureInspector.svelte +520 -0
  26. package/dist/app/ProcedureInspector.svelte.d.ts +15 -0
  27. package/dist/app/ProcedureNode.svelte +283 -0
  28. package/dist/app/ProcedureNode.svelte.d.ts +26 -0
  29. package/dist/app/Realm.types.js +1 -0
  30. package/dist/app/RealmIndicator.svelte +81 -0
  31. package/dist/app/RealmIndicator.svelte.d.ts +10 -0
  32. package/dist/app/RealmSwitcher.svelte +354 -0
  33. package/dist/app/RealmSwitcher.svelte.d.ts +16 -0
  34. package/dist/app/SemanticConversation.types.js +1 -0
  35. package/dist/app/SemanticSearchInput.svelte +630 -0
  36. package/dist/app/SemanticSearchInput.svelte.d.ts +20 -0
  37. package/dist/app/SemanticSearchInput.types.js +1 -0
  38. package/dist/app/SemanticTimeline.svelte +426 -0
  39. package/dist/app/SemanticTimeline.svelte.d.ts +13 -0
  40. package/dist/app/SettingsPanel.svelte +330 -0
  41. package/dist/app/SettingsPanel.svelte.d.ts +12 -0
  42. package/dist/app/SettingsPanel.types.js +1 -0
  43. package/dist/app/SubCanvas.svelte +457 -0
  44. package/dist/app/SubCanvas.svelte.d.ts +48 -0
  45. package/dist/app/SubCanvas.types.js +1 -0
  46. package/dist/app/Sync.types.js +1 -0
  47. package/dist/app/SyncIndicator.svelte +219 -0
  48. package/dist/app/SyncIndicator.svelte.d.ts +15 -0
  49. package/dist/app/SyncTimeline.svelte +299 -0
  50. package/dist/app/SyncTimeline.svelte.d.ts +12 -0
  51. package/dist/app/TagCloud.svelte +287 -0
  52. package/dist/app/TagCloud.svelte.d.ts +12 -0
  53. package/dist/app/WorkModeToggle.svelte +188 -0
  54. package/dist/app/WorkModeToggle.svelte.d.ts +17 -0
  55. package/dist/data/List.svelte +104 -0
  56. package/dist/data/List.svelte.d.ts +18 -0
  57. package/dist/data/ListItem.svelte +130 -0
  58. package/dist/data/ListItem.svelte.d.ts +12 -0
  59. package/dist/data/Table.svelte +241 -0
  60. package/dist/data/Table.svelte.d.ts +17 -0
  61. package/dist/data/index.d.ts +3 -0
  62. package/dist/data/index.js +3 -0
  63. package/dist/disclosure/Accordion.svelte +48 -0
  64. package/dist/disclosure/Accordion.svelte.d.ts +15 -0
  65. package/dist/feedback/Badge.svelte +60 -0
  66. package/dist/feedback/Badge.svelte.d.ts +14 -0
  67. package/dist/feedback/Callout.svelte +52 -0
  68. package/dist/feedback/Callout.svelte.d.ts +12 -0
  69. package/dist/feedback/EmptyState.svelte +47 -0
  70. package/dist/feedback/EmptyState.svelte.d.ts +12 -0
  71. package/dist/feedback/ProgressBar.svelte +95 -0
  72. package/dist/feedback/ProgressBar.svelte.d.ts +16 -0
  73. package/dist/forms/FileUpload.svelte +99 -0
  74. package/dist/forms/FileUpload.svelte.d.ts +18 -0
  75. package/dist/forms/RadioGroup.svelte +84 -0
  76. package/dist/forms/RadioGroup.svelte.d.ts +19 -0
  77. package/dist/icons/NerdFont.svelte +44 -0
  78. package/dist/icons/NerdFont.svelte.d.ts +13 -0
  79. package/dist/icons/index.d.ts +1 -0
  80. package/dist/icons/index.js +1 -0
  81. package/dist/index.d.ts +76 -0
  82. package/dist/index.js +70 -6212
  83. package/dist/layout/Box.svelte +207 -0
  84. package/dist/layout/Box.svelte.d.ts +22 -0
  85. package/dist/layout/Sidebar.svelte +210 -0
  86. package/dist/layout/Sidebar.svelte.d.ts +22 -0
  87. package/dist/layout/SplitPane.svelte +64 -0
  88. package/dist/layout/SplitPane.svelte.d.ts +12 -0
  89. package/dist/layout/StatusBar.svelte +83 -0
  90. package/dist/layout/StatusBar.svelte.d.ts +12 -0
  91. package/dist/layout/StatusBarItem.svelte +146 -0
  92. package/dist/layout/StatusBarItem.svelte.d.ts +15 -0
  93. package/dist/layout/StatusBarSpacer.svelte +38 -0
  94. package/dist/layout/StatusBarSpacer.svelte.d.ts +3 -0
  95. package/dist/layout/Tabs.svelte +254 -0
  96. package/dist/layout/Tabs.svelte.d.ts +21 -0
  97. package/dist/layout/Tabs.types.js +1 -0
  98. package/dist/layout/TitleBar.svelte +422 -0
  99. package/dist/layout/TitleBar.svelte.d.ts +22 -0
  100. package/dist/layout/index.d.ts +9 -0
  101. package/dist/layout/index.js +8 -0
  102. package/dist/motion/index.d.ts +1 -0
  103. package/dist/motion/index.js +1 -0
  104. package/dist/motion/spring.js +116 -0
  105. package/dist/overlays/ContextMenu.svelte +268 -0
  106. package/dist/overlays/ContextMenu.svelte.d.ts +17 -0
  107. package/dist/overlays/Dialog.svelte +264 -0
  108. package/dist/overlays/Dialog.svelte.d.ts +20 -0
  109. package/dist/overlays/Menu.svelte +274 -0
  110. package/dist/overlays/Menu.svelte.d.ts +26 -0
  111. package/dist/overlays/Menu.types.js +1 -0
  112. package/dist/overlays/Popover.svelte +158 -0
  113. package/dist/overlays/Popover.svelte.d.ts +21 -0
  114. package/dist/overlays/Toast.svelte +179 -0
  115. package/dist/overlays/Toast.svelte.d.ts +19 -0
  116. package/dist/overlays/Tooltip.svelte +114 -0
  117. package/dist/overlays/Tooltip.svelte.d.ts +17 -0
  118. package/dist/overlays/index.d.ts +7 -0
  119. package/dist/overlays/index.js +6 -0
  120. package/dist/primitives/Button.svelte +217 -0
  121. package/dist/primitives/Button.svelte.d.ts +13 -0
  122. package/dist/primitives/ContextMenu.svelte +242 -0
  123. package/dist/primitives/ContextMenu.svelte.d.ts +18 -0
  124. package/dist/primitives/ContextMenu.types.js +1 -0
  125. package/dist/primitives/Input.svelte +468 -0
  126. package/dist/primitives/Input.svelte.d.ts +21 -0
  127. package/dist/primitives/MarkdownEditor.svelte +781 -0
  128. package/dist/primitives/MarkdownEditor.svelte.d.ts +21 -0
  129. package/dist/primitives/MarkdownEditor.types.js +1 -0
  130. package/dist/primitives/SearchInput.svelte +623 -0
  131. package/dist/primitives/SearchInput.svelte.d.ts +24 -0
  132. package/dist/primitives/Select.svelte +336 -0
  133. package/dist/primitives/Select.svelte.d.ts +18 -0
  134. package/dist/primitives/Text.svelte +177 -0
  135. package/dist/primitives/Text.svelte.d.ts +26 -0
  136. package/dist/primitives/Toggle.svelte +138 -0
  137. package/dist/primitives/Toggle.svelte.d.ts +9 -0
  138. package/dist/primitives/index.d.ts +9 -0
  139. package/dist/primitives/index.js +7 -0
  140. package/dist/primitives/search-input-types.js +1 -0
  141. package/dist/surfaces/ChatPane.svelte +520 -0
  142. package/dist/surfaces/ChatPane.svelte.d.ts +15 -0
  143. package/dist/surfaces/ChatPane.types.js +1 -0
  144. package/dist/surfaces/GlassPanel.svelte +118 -0
  145. package/dist/surfaces/GlassPanel.svelte.d.ts +19 -0
  146. package/dist/surfaces/Pane.svelte +172 -0
  147. package/dist/surfaces/Pane.svelte.d.ts +25 -0
  148. package/dist/surfaces/index.d.ts +4 -0
  149. package/dist/surfaces/index.js +3 -0
  150. package/dist/telemetry/correlation.js +26 -0
  151. package/dist/telemetry/index.d.ts +4 -4
  152. package/dist/telemetry/index.js +20 -101
  153. package/dist/telemetry/sampling.js +58 -0
  154. package/dist/telemetry/tracer.d.ts +16 -1
  155. package/dist/telemetry/tracer.js +112 -0
  156. package/dist/tokens.css +123 -0
  157. package/dist/tui-tokens.css +36 -0
  158. package/dist/useTui.js +31 -0
  159. package/package.json +32 -22
  160. package/dist/design-dojo.css +0 -1
  161. package/dist/enforce/index.d.ts +0 -75
  162. package/dist/enforce/known-components.d.ts +0 -7
  163. package/dist/enforce/rules/no-local-components.d.ts +0 -29
  164. package/dist/enforce/rules/prefer-design-dojo-imports.d.ts +0 -27
  165. package/dist/enforce.js +0 -132
  166. package/dist/lib/app/CanvasBreadcrumb.svelte.d.ts +0 -1
  167. package/dist/lib/app/ChatInput.svelte.d.ts +0 -1
  168. package/dist/lib/app/ChatView.svelte.d.ts +0 -1
  169. package/dist/lib/app/ConversationGraph.svelte.d.ts +0 -1
  170. package/dist/lib/app/FirstRunWizard.svelte.d.ts +0 -1
  171. package/dist/lib/app/MemorySidebar.svelte.d.ts +0 -1
  172. package/dist/lib/app/PeerStatusPanel.svelte.d.ts +0 -1
  173. package/dist/lib/app/ProcedureCanvas.svelte.d.ts +0 -1
  174. package/dist/lib/app/ProcedureEditor.svelte.d.ts +0 -1
  175. package/dist/lib/app/ProcedureInspector.svelte.d.ts +0 -1
  176. package/dist/lib/app/ProcedureNode.svelte.d.ts +0 -1
  177. package/dist/lib/app/RealmIndicator.svelte.d.ts +0 -1
  178. package/dist/lib/app/RealmSwitcher.svelte.d.ts +0 -1
  179. package/dist/lib/app/SemanticSearchInput.svelte.d.ts +0 -1
  180. package/dist/lib/app/SemanticTimeline.svelte.d.ts +0 -1
  181. package/dist/lib/app/SettingsPanel.svelte.d.ts +0 -1
  182. package/dist/lib/app/SubCanvas.svelte.d.ts +0 -1
  183. package/dist/lib/app/SyncIndicator.svelte.d.ts +0 -1
  184. package/dist/lib/app/SyncTimeline.svelte.d.ts +0 -1
  185. package/dist/lib/app/TagCloud.svelte.d.ts +0 -1
  186. package/dist/lib/app/WorkModeToggle.svelte.d.ts +0 -1
  187. package/dist/lib/data/List.svelte.d.ts +0 -1
  188. package/dist/lib/data/ListItem.svelte.d.ts +0 -1
  189. package/dist/lib/data/Table.svelte.d.ts +0 -1
  190. package/dist/lib/data/index.d.ts +0 -3
  191. package/dist/lib/disclosure/Accordion.svelte.d.ts +0 -1
  192. package/dist/lib/feedback/Badge.svelte.d.ts +0 -1
  193. package/dist/lib/feedback/Callout.svelte.d.ts +0 -1
  194. package/dist/lib/feedback/EmptyState.svelte.d.ts +0 -1
  195. package/dist/lib/feedback/ProgressBar.svelte.d.ts +0 -1
  196. package/dist/lib/forms/FileUpload.svelte.d.ts +0 -1
  197. package/dist/lib/forms/RadioGroup.svelte.d.ts +0 -1
  198. package/dist/lib/icons/NerdFont.svelte.d.ts +0 -1
  199. package/dist/lib/icons/index.d.ts +0 -1
  200. package/dist/lib/index.d.ts +0 -76
  201. package/dist/lib/layout/Box.svelte.d.ts +0 -1
  202. package/dist/lib/layout/Sidebar.svelte.d.ts +0 -1
  203. package/dist/lib/layout/SplitPane.svelte.d.ts +0 -1
  204. package/dist/lib/layout/StatusBar.svelte.d.ts +0 -1
  205. package/dist/lib/layout/StatusBarItem.svelte.d.ts +0 -1
  206. package/dist/lib/layout/StatusBarSpacer.svelte.d.ts +0 -1
  207. package/dist/lib/layout/Tabs.svelte.d.ts +0 -1
  208. package/dist/lib/layout/TitleBar.svelte.d.ts +0 -1
  209. package/dist/lib/layout/index.d.ts +0 -9
  210. package/dist/lib/motion/index.d.ts +0 -1
  211. package/dist/lib/overlays/ContextMenu.svelte.d.ts +0 -1
  212. package/dist/lib/overlays/Dialog.svelte.d.ts +0 -1
  213. package/dist/lib/overlays/Menu.svelte.d.ts +0 -1
  214. package/dist/lib/overlays/Popover.svelte.d.ts +0 -1
  215. package/dist/lib/overlays/Toast.svelte.d.ts +0 -1
  216. package/dist/lib/overlays/Tooltip.svelte.d.ts +0 -1
  217. package/dist/lib/overlays/index.d.ts +0 -7
  218. package/dist/lib/primitives/Button.svelte.d.ts +0 -1
  219. package/dist/lib/primitives/ContextMenu.svelte.d.ts +0 -1
  220. package/dist/lib/primitives/Input.svelte.d.ts +0 -1
  221. package/dist/lib/primitives/MarkdownEditor.svelte.d.ts +0 -1
  222. package/dist/lib/primitives/SearchInput.svelte.d.ts +0 -1
  223. package/dist/lib/primitives/Select.svelte.d.ts +0 -1
  224. package/dist/lib/primitives/Text.svelte.d.ts +0 -1
  225. package/dist/lib/primitives/Toggle.svelte.d.ts +0 -1
  226. package/dist/lib/primitives/index.d.ts +0 -9
  227. package/dist/lib/surfaces/ChatPane.svelte.d.ts +0 -1
  228. package/dist/lib/surfaces/GlassPanel.svelte.d.ts +0 -1
  229. package/dist/lib/surfaces/Pane.svelte.d.ts +0 -1
  230. package/dist/lib/surfaces/index.d.ts +0 -4
  231. /package/dist/{lib/app → app}/CanvasBreadcrumb.types.d.ts +0 -0
  232. /package/dist/{lib/app → app}/ChatView.types.d.ts +0 -0
  233. /package/dist/{lib/app → app}/FirstRunWizard.types.d.ts +0 -0
  234. /package/dist/{lib/app → app}/MemorySidebar.types.d.ts +0 -0
  235. /package/dist/{lib/app → app}/ProcedureCanvas.types.d.ts +0 -0
  236. /package/dist/{lib/app → app}/ProcedureEditor.types.d.ts +0 -0
  237. /package/dist/{lib/app → app}/Realm.types.d.ts +0 -0
  238. /package/dist/{lib/app → app}/SemanticConversation.types.d.ts +0 -0
  239. /package/dist/{lib/app → app}/SemanticSearchInput.types.d.ts +0 -0
  240. /package/dist/{lib/app → app}/SettingsPanel.types.d.ts +0 -0
  241. /package/dist/{lib/app → app}/SubCanvas.types.d.ts +0 -0
  242. /package/dist/{lib/app → app}/Sync.types.d.ts +0 -0
  243. /package/dist/{lib/layout → layout}/Tabs.types.d.ts +0 -0
  244. /package/dist/{lib/motion → motion}/spring.d.ts +0 -0
  245. /package/dist/{lib/overlays → overlays}/Menu.types.d.ts +0 -0
  246. /package/dist/{lib/primitives → primitives}/ContextMenu.types.d.ts +0 -0
  247. /package/dist/{lib/primitives → primitives}/MarkdownEditor.types.d.ts +0 -0
  248. /package/dist/{lib/primitives → primitives}/search-input-types.d.ts +0 -0
  249. /package/dist/{lib/surfaces → surfaces}/ChatPane.types.d.ts +0 -0
  250. /package/dist/{lib/useTui.d.ts → useTui.d.ts} +0 -0
@@ -0,0 +1,219 @@
1
+ <!--
2
+ SyncIndicator — Compact persistent indicator showing Hyperswarm sync state.
3
+
4
+ States:
5
+ - "synced" — green steady pulse: all peers up to date
6
+ - "syncing" — animated spinner: active data exchange
7
+ - "offline" — grey: no peers reachable
8
+ - "conflict" — yellow/amber: conflicting records need resolution
9
+
10
+ Displays a peer-count badge. Click fires `onclick` so the host can open
11
+ PeerStatusPanel.
12
+ -->
13
+ <script lang="ts">
14
+ import type { SyncStatus } from "./Sync.types.js";
15
+
16
+ interface Props {
17
+ status: SyncStatus;
18
+ /** Number of currently connected peers. */
19
+ peerCount?: number;
20
+ /** Accessible label prefix. Defaults to "Sync status". */
21
+ label?: string;
22
+ /** Custom CSS class. */
23
+ class?: string;
24
+ /** Fired when the indicator is activated (click / Enter / Space). */
25
+ onclick?: () => void;
26
+ }
27
+
28
+ let {
29
+ status,
30
+ peerCount = 0,
31
+ label = "Sync status",
32
+ class: className = "",
33
+ onclick,
34
+ }: Props = $props();
35
+
36
+ const STATUS_LABEL: Record<SyncStatus, string> = {
37
+ synced: "synced",
38
+ syncing: "syncing",
39
+ offline: "offline",
40
+ conflict: "conflict",
41
+ };
42
+
43
+ const PEER_LABEL = $derived(
44
+ peerCount === 0
45
+ ? "no devices connected"
46
+ : peerCount === 1
47
+ ? "1 device connected"
48
+ : `${peerCount} devices connected`,
49
+ );
50
+
51
+ const ariaLabel = $derived(`${label}: ${STATUS_LABEL[status]}, ${PEER_LABEL}`);
52
+
53
+ function handleKeydown(e: KeyboardEvent) {
54
+ if (e.key === "Enter" || e.key === " ") {
55
+ e.preventDefault();
56
+ onclick?.();
57
+ }
58
+ }
59
+ </script>
60
+
61
+ <div
62
+ class="sync-indicator sync-indicator--{status} {className}"
63
+ role="button"
64
+ tabindex="0"
65
+ aria-label={ariaLabel}
66
+ onclick={onclick}
67
+ onkeydown={handleKeydown}
68
+ >
69
+ <!-- Animated ring + icon -->
70
+ <div class="sync-indicator__ring" aria-hidden="true">
71
+ {#if status === "syncing"}
72
+ <div class="sync-indicator__spinner"></div>
73
+ {:else}
74
+ <div class="sync-indicator__dot"></div>
75
+ {/if}
76
+ </div>
77
+
78
+ <!-- Textual status -->
79
+ <span class="sync-indicator__label">
80
+ {STATUS_LABEL[status]}
81
+ </span>
82
+
83
+ <!-- Peer-count badge -->
84
+ {#if peerCount > 0}
85
+ <span class="sync-indicator__badge" aria-hidden="true">
86
+ {peerCount}
87
+ </span>
88
+ {/if}
89
+ </div>
90
+
91
+ <style>
92
+ .sync-indicator {
93
+ display: inline-flex;
94
+ align-items: center;
95
+ gap: var(--space-2, 8px);
96
+ padding: 6px var(--space-3, 12px) 6px 8px;
97
+ border-radius: var(--radius-full, 9999px);
98
+ border: 1px solid var(--color-border, #2a2a2a);
99
+ background: var(--surface-1, #141414);
100
+ cursor: pointer;
101
+ user-select: none;
102
+ font-size: var(--text-sm, 14px);
103
+ font-weight: 500;
104
+ color: var(--color-text-muted, #888);
105
+ outline: none;
106
+ transition: border-color 0.15s, background 0.15s;
107
+ }
108
+
109
+ .sync-indicator:hover,
110
+ .sync-indicator:focus-visible {
111
+ background: var(--surface-2, #1e1e1e);
112
+ border-color: var(--color-text-muted, #888);
113
+ outline: none;
114
+ }
115
+
116
+ @media (prefers-reduced-motion: reduce) {
117
+ .sync-indicator { transition: none; }
118
+ }
119
+
120
+ /* ── Ring wrapper ── */
121
+ .sync-indicator__ring {
122
+ position: relative;
123
+ width: 16px;
124
+ height: 16px;
125
+ display: flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ flex-shrink: 0;
129
+ }
130
+
131
+ /* ── Steady dot ── */
132
+ .sync-indicator__dot {
133
+ width: 10px;
134
+ height: 10px;
135
+ border-radius: 50%;
136
+ background: currentColor;
137
+ }
138
+
139
+ /* ── Spinning ring (syncing) ── */
140
+ .sync-indicator__spinner {
141
+ width: 14px;
142
+ height: 14px;
143
+ border-radius: 50%;
144
+ border: 2px solid transparent;
145
+ border-top-color: currentColor;
146
+ border-right-color: currentColor;
147
+ animation: sync-spin 0.9s linear infinite;
148
+ }
149
+
150
+ @keyframes sync-spin {
151
+ to { transform: rotate(360deg); }
152
+ }
153
+
154
+ @media (prefers-reduced-motion: reduce) {
155
+ .sync-indicator__spinner {
156
+ animation: none;
157
+ border-top-color: currentColor;
158
+ border-right-color: transparent;
159
+ }
160
+ }
161
+
162
+ /* ── Label ── */
163
+ .sync-indicator__label {
164
+ line-height: 1;
165
+ }
166
+
167
+ /* ── Badge ── */
168
+ .sync-indicator__badge {
169
+ display: inline-flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ min-width: 18px;
173
+ height: 18px;
174
+ padding: 0 5px;
175
+ border-radius: var(--radius-full, 9999px);
176
+ font-size: 11px;
177
+ font-weight: 700;
178
+ font-family: var(--font-mono, monospace);
179
+ background: currentColor;
180
+ /* invert text so it reads over the badge colour */
181
+ color: var(--surface-0, #0a0a0a);
182
+ }
183
+
184
+ /* ── Status colour variants ── */
185
+ .sync-indicator--synced {
186
+ color: var(--color-success, #22c55e);
187
+ border-color: color-mix(in srgb, var(--color-success, #22c55e) 30%, transparent);
188
+ }
189
+
190
+ .sync-indicator--synced .sync-indicator__dot {
191
+ animation: sync-pulse 2.4s ease-in-out infinite;
192
+ }
193
+
194
+ @keyframes sync-pulse {
195
+ 0%, 100% { opacity: 1; transform: scale(1); }
196
+ 50% { opacity: 0.5; transform: scale(0.75); }
197
+ }
198
+
199
+ @media (prefers-reduced-motion: reduce) {
200
+ .sync-indicator--synced .sync-indicator__dot {
201
+ animation: none;
202
+ }
203
+ }
204
+
205
+ .sync-indicator--syncing {
206
+ color: var(--color-info, #3b82f6);
207
+ border-color: color-mix(in srgb, var(--color-info, #3b82f6) 30%, transparent);
208
+ }
209
+
210
+ .sync-indicator--offline {
211
+ color: var(--color-text-muted, #888);
212
+ border-color: var(--color-border, #2a2a2a);
213
+ }
214
+
215
+ .sync-indicator--conflict {
216
+ color: var(--color-warning, #f59e0b);
217
+ border-color: color-mix(in srgb, var(--color-warning, #f59e0b) 30%, transparent);
218
+ }
219
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { SyncStatus } from "./Sync.types.js";
2
+ interface Props {
3
+ status: SyncStatus;
4
+ /** Number of currently connected peers. */
5
+ peerCount?: number;
6
+ /** Accessible label prefix. Defaults to "Sync status". */
7
+ label?: string;
8
+ /** Custom CSS class. */
9
+ class?: string;
10
+ /** Fired when the indicator is activated (click / Enter / Space). */
11
+ onclick?: () => void;
12
+ }
13
+ declare const SyncIndicator: import("svelte").Component<Props, {}, "">;
14
+ type SyncIndicator = ReturnType<typeof SyncIndicator>;
15
+ export default SyncIndicator;
@@ -0,0 +1,299 @@
1
+ <!--
2
+ SyncTimeline — Visual timeline of recent Hyperswarm sync events.
3
+
4
+ Shows what was synced (memories, procedures, state, ledger), when,
5
+ to/from which peer, and the direction of data flow. Useful for
6
+ debugging sync issues.
7
+
8
+ Features:
9
+ - Chronological list, newest first
10
+ - Colour-coded by record type
11
+ - Direction indicator (in / out)
12
+ - Peer name resolved from peer list
13
+ - Empty state
14
+ - Accessible
15
+ -->
16
+ <script lang="ts">
17
+ import type { Peer, SyncEvent, SyncRecordType } from "./Sync.types.js";
18
+
19
+ interface Props {
20
+ events: SyncEvent[];
21
+ peers: Peer[];
22
+ /** Maximum events to show. Default: 50. */
23
+ maxEvents?: number;
24
+ /** Custom CSS class. */
25
+ class?: string;
26
+ }
27
+
28
+ let { events, peers, maxEvents = 50, class: className = "" }: Props = $props();
29
+
30
+ // ── Colour per record type ────────────────────────────────────────────────
31
+ const TYPE_COLOR: Record<SyncRecordType, string> = {
32
+ memory: "#6366f1",
33
+ procedure: "#22c55e",
34
+ state: "#3b82f6",
35
+ ledger: "#f59e0b",
36
+ };
37
+
38
+ const TYPE_ICON: Record<SyncRecordType, string> = {
39
+ memory: "◈",
40
+ procedure: "⬡",
41
+ state: "◉",
42
+ ledger: "▣",
43
+ };
44
+
45
+ // ── Peer lookup ───────────────────────────────────────────────────────────
46
+ const peerMap = $derived(new Map(peers.map((p) => [p.id, p])));
47
+
48
+ function peerName(id: string): string {
49
+ return peerMap.get(id)?.name ?? id.slice(0, 8);
50
+ }
51
+
52
+ // ── Sorted events ─────────────────────────────────────────────────────────
53
+ const sorted = $derived(
54
+ [...events]
55
+ .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
56
+ .slice(0, maxEvents),
57
+ );
58
+
59
+ // ── Helpers ───────────────────────────────────────────────────────────────
60
+ function formatTime(d: Date): string {
61
+ return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
62
+ }
63
+
64
+ function formatDate(d: Date): string {
65
+ return d.toLocaleDateString([], { month: "short", day: "numeric" });
66
+ }
67
+
68
+ function isToday(d: Date): boolean {
69
+ const now = new Date();
70
+ return (
71
+ d.getFullYear() === now.getFullYear() &&
72
+ d.getMonth() === now.getMonth() &&
73
+ d.getDate() === now.getDate()
74
+ );
75
+ }
76
+
77
+ function formatTimestamp(d: Date): string {
78
+ return isToday(d) ? formatTime(d) : `${formatDate(d)} ${formatTime(d)}`;
79
+ }
80
+ </script>
81
+
82
+ <div class="sync-tl {className}" aria-label="Sync event timeline">
83
+ <header class="sync-tl__header">
84
+ <span class="sync-tl__title">Sync History</span>
85
+ {#if sorted.length > 0}
86
+ <span class="sync-tl__count" aria-label="{sorted.length}{events.length > maxEvents ? '+' : ''} events{events.length > maxEvents ? ', showing newest ' + maxEvents : ''}">
87
+ {sorted.length}{events.length > maxEvents ? "+" : ""}
88
+ </span>
89
+ {/if}
90
+ </header>
91
+
92
+ {#if sorted.length === 0}
93
+ <p class="sync-tl__empty">No sync events recorded yet.</p>
94
+ {:else}
95
+ <ol class="sync-tl__list" role="list">
96
+ {#each sorted as ev (ev.id)}
97
+ {@const color = TYPE_COLOR[ev.type]}
98
+ {@const icon = TYPE_ICON[ev.type]}
99
+ <li
100
+ class="sync-tl__event"
101
+ aria-label="{ev.direction === 'in' ? 'Received' : 'Sent'} {ev.count} {ev.type} record{ev.count !== 1 ? 's' : ''} {ev.direction === 'in' ? 'from' : 'to'} {peerName(ev.peerId)} at {formatTimestamp(ev.timestamp)}"
102
+ >
103
+ <!-- Spine -->
104
+ <div class="sync-tl__spine" aria-hidden="true">
105
+ <span class="sync-tl__icon" style:color={color}>{icon}</span>
106
+ <span class="sync-tl__line"></span>
107
+ </div>
108
+
109
+ <!-- Body -->
110
+ <div class="sync-tl__body">
111
+ <div class="sync-tl__row">
112
+ <!-- Type tag -->
113
+ <span
114
+ class="sync-tl__type-tag"
115
+ style:--type-color={color}
116
+ >{ev.type}</span>
117
+
118
+ <!-- Direction + count -->
119
+ <span class="sync-tl__direction" aria-hidden="true">
120
+ {ev.direction === "in" ? "↓" : "↑"}
121
+ </span>
122
+ <span class="sync-tl__count-badge">
123
+ {ev.count} record{ev.count !== 1 ? "s" : ""}
124
+ </span>
125
+
126
+ <!-- Peer -->
127
+ <span class="sync-tl__peer">
128
+ {ev.direction === "in" ? "from" : "to"}
129
+ <strong>{peerName(ev.peerId)}</strong>
130
+ </span>
131
+
132
+ <!-- Timestamp -->
133
+ <time
134
+ class="sync-tl__time"
135
+ datetime={ev.timestamp.toISOString()}
136
+ >{formatTimestamp(ev.timestamp)}</time>
137
+ </div>
138
+ </div>
139
+ </li>
140
+ {/each}
141
+ </ol>
142
+ {/if}
143
+ </div>
144
+
145
+ <style>
146
+ .sync-tl {
147
+ display: flex;
148
+ flex-direction: column;
149
+ background: var(--surface-1, #141414);
150
+ border: 1px solid var(--color-border, #2a2a2a);
151
+ border-radius: var(--radius-lg, 16px);
152
+ overflow: hidden;
153
+ }
154
+
155
+ /* ── Header ── */
156
+ .sync-tl__header {
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: space-between;
160
+ padding: var(--space-3, 12px) var(--space-4, 16px);
161
+ border-bottom: 1px solid var(--color-border, #2a2a2a);
162
+ background: var(--surface-2, #1e1e1e);
163
+ }
164
+
165
+ .sync-tl__title {
166
+ font-size: var(--text-sm, 14px);
167
+ font-weight: 600;
168
+ color: var(--color-text, #e8e8e8);
169
+ }
170
+
171
+ .sync-tl__count {
172
+ font-size: var(--text-xs, 12px);
173
+ font-family: var(--font-mono, monospace);
174
+ font-weight: 700;
175
+ color: var(--color-text-muted, #888);
176
+ background: var(--surface-1, #141414);
177
+ border: 1px solid var(--color-border, #2a2a2a);
178
+ border-radius: var(--radius-full, 9999px);
179
+ padding: 2px 8px;
180
+ }
181
+
182
+ /* ── Empty ── */
183
+ .sync-tl__empty {
184
+ margin: 0;
185
+ padding: var(--space-6, 24px) var(--space-4, 16px);
186
+ color: var(--color-text-subtle, #555);
187
+ font-size: var(--text-sm, 14px);
188
+ text-align: center;
189
+ }
190
+
191
+ /* ── List ── */
192
+ .sync-tl__list {
193
+ list-style: none;
194
+ margin: 0;
195
+ padding: var(--space-3, 12px) var(--space-4, 16px);
196
+ display: flex;
197
+ flex-direction: column;
198
+ gap: 0;
199
+ max-height: 420px;
200
+ overflow-y: auto;
201
+ }
202
+
203
+ /* ── Event row ── */
204
+ .sync-tl__event {
205
+ display: grid;
206
+ grid-template-columns: 20px 1fr;
207
+ gap: var(--space-3, 12px);
208
+ min-height: 40px;
209
+ }
210
+
211
+ /* ── Spine ── */
212
+ .sync-tl__spine {
213
+ display: flex;
214
+ flex-direction: column;
215
+ align-items: center;
216
+ padding-top: 10px;
217
+ }
218
+
219
+ .sync-tl__icon {
220
+ font-size: 14px;
221
+ line-height: 1;
222
+ flex-shrink: 0;
223
+ z-index: 1;
224
+ }
225
+
226
+ .sync-tl__line {
227
+ flex: 1;
228
+ width: 1px;
229
+ background: var(--color-border, #2a2a2a);
230
+ margin-top: 3px;
231
+ min-height: 8px;
232
+ }
233
+
234
+ .sync-tl__event:last-child .sync-tl__line {
235
+ display: none;
236
+ }
237
+
238
+ /* ── Body ── */
239
+ .sync-tl__body {
240
+ padding: 8px 0 16px;
241
+ display: flex;
242
+ flex-direction: column;
243
+ gap: var(--space-1, 4px);
244
+ }
245
+
246
+ .sync-tl__row {
247
+ display: flex;
248
+ align-items: center;
249
+ flex-wrap: wrap;
250
+ gap: var(--space-1, 4px) var(--space-2, 8px);
251
+ }
252
+
253
+ /* ── Type tag ── */
254
+ .sync-tl__type-tag {
255
+ font-size: var(--text-xs, 12px);
256
+ font-weight: 700;
257
+ padding: 2px 8px;
258
+ border-radius: var(--radius-full, 9999px);
259
+ background: color-mix(in srgb, var(--type-color, #6366f1) 15%, var(--surface-2, #1e1e1e));
260
+ color: var(--type-color, #6366f1);
261
+ border: 1px solid color-mix(in srgb, var(--type-color, #6366f1) 30%, transparent);
262
+ white-space: nowrap;
263
+ text-transform: capitalize;
264
+ }
265
+
266
+ /* ── Direction ── */
267
+ .sync-tl__direction {
268
+ font-size: var(--text-sm, 14px);
269
+ font-weight: 700;
270
+ color: var(--color-text-muted, #888);
271
+ }
272
+
273
+ /* ── Record count ── */
274
+ .sync-tl__count-badge {
275
+ font-size: var(--text-xs, 12px);
276
+ font-family: var(--font-mono, monospace);
277
+ color: var(--color-text-muted, #888);
278
+ }
279
+
280
+ /* ── Peer ── */
281
+ .sync-tl__peer {
282
+ font-size: var(--text-xs, 12px);
283
+ color: var(--color-text-subtle, #555);
284
+ }
285
+
286
+ .sync-tl__peer strong {
287
+ color: var(--color-text, #e8e8e8);
288
+ font-weight: 600;
289
+ }
290
+
291
+ /* ── Timestamp ── */
292
+ .sync-tl__time {
293
+ font-size: 11px;
294
+ font-family: var(--font-mono, monospace);
295
+ color: var(--color-text-subtle, #555);
296
+ margin-left: auto;
297
+ white-space: nowrap;
298
+ }
299
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { Peer, SyncEvent } from "./Sync.types.js";
2
+ interface Props {
3
+ events: SyncEvent[];
4
+ peers: Peer[];
5
+ /** Maximum events to show. Default: 50. */
6
+ maxEvents?: number;
7
+ /** Custom CSS class. */
8
+ class?: string;
9
+ }
10
+ declare const SyncTimeline: import("svelte").Component<Props, {}, "">;
11
+ type SyncTimeline = ReturnType<typeof SyncTimeline>;
12
+ export default SyncTimeline;