@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,542 @@
1
+ <!--
2
+ ChatView — Chat message list with markdown rendering and streaming indicator.
3
+
4
+ Modes:
5
+ - "bounded" (default): terminal-pane style with crisp bordered message boxes,
6
+ keyboard navigation (j/k / arrow keys between messages, Enter expand/collapse),
7
+ and smart scroll lock.
8
+ - "bubble": classic right/left chat bubble layout.
9
+
10
+ Shared features:
11
+ - Basic markdown: **bold**, *italic*, `inline code`, ```code block```
12
+ - Streaming indicator: animated bouncing dots on the last message
13
+ - Auto-scroll to bottom; scroll-lock on scroll-up
14
+ - "▼ N new" jump button when locked
15
+ -->
16
+ <script lang="ts">
17
+ import type { ChatViewMessage, ChatViewMode } from "./ChatView.types.js";
18
+
19
+ interface Props {
20
+ messages: ChatViewMessage[];
21
+ /**
22
+ * Layout mode.
23
+ * - "bounded": terminal-pane message boxes with keyboard navigation (default).
24
+ * - "bubble": classic chat bubble layout.
25
+ */
26
+ mode?: ChatViewMode;
27
+ /** Show author + timestamps in message headers. Default: true */
28
+ showTimestamps?: boolean;
29
+ /** Custom CSS class. */
30
+ class?: string;
31
+ /** Fired when user clicks the "jump to latest" indicator. */
32
+ onscrolltolatest?: () => void;
33
+ }
34
+
35
+ let {
36
+ messages,
37
+ mode = "bounded",
38
+ showTimestamps = true,
39
+ class: className = "",
40
+ onscrolltolatest,
41
+ }: Props = $props();
42
+
43
+ // ── Scroll state ──────────────────────────────────────────────────────────
44
+ let scrollEl: HTMLDivElement | undefined = $state();
45
+ let atBottom = $state(true);
46
+ let newCount = $state(0);
47
+ let prevLength = $state(0);
48
+
49
+ $effect(() => {
50
+ const len = messages.length;
51
+ if (len !== prevLength) {
52
+ const added = len - prevLength;
53
+ prevLength = len;
54
+ if (atBottom) {
55
+ newCount = 0;
56
+ scheduleScrollToBottom();
57
+ } else if (added > 0) {
58
+ newCount += added;
59
+ }
60
+ }
61
+ });
62
+
63
+ function scheduleScrollToBottom() {
64
+ if (typeof requestAnimationFrame !== "undefined") {
65
+ requestAnimationFrame(() => scrollToBottom());
66
+ }
67
+ }
68
+
69
+ function scrollToBottom() {
70
+ if (scrollEl) scrollEl.scrollTop = scrollEl.scrollHeight;
71
+ }
72
+
73
+ function handleScrollToLatest() {
74
+ atBottom = true;
75
+ newCount = 0;
76
+ scrollToBottom();
77
+ onscrolltolatest?.();
78
+ }
79
+
80
+ function handleScroll() {
81
+ if (!scrollEl) return;
82
+ const { scrollTop, scrollHeight, clientHeight } = scrollEl;
83
+ const wasAtBottom = atBottom;
84
+ atBottom = scrollHeight - scrollTop - clientHeight < 8;
85
+ if (atBottom && !wasAtBottom) newCount = 0;
86
+ }
87
+
88
+ function handleKeydown(e: KeyboardEvent) {
89
+ if (!scrollEl) return;
90
+ const h = scrollEl.clientHeight;
91
+ switch (e.key) {
92
+ case "PageUp": e.preventDefault(); scrollEl.scrollBy({ top: -h, behavior: "smooth" }); break;
93
+ case "PageDown": e.preventDefault(); scrollEl.scrollBy({ top: h, behavior: "smooth" }); break;
94
+ case "Home": e.preventDefault(); scrollEl.scrollTo({ top: 0, behavior: "smooth" }); break;
95
+ case "End": e.preventDefault(); scrollToBottom(); break;
96
+ }
97
+ }
98
+
99
+ // ── Bounded-mode state ────────────────────────────────────────────────────
100
+ /** Index of the currently keyboard-focused message (-1 = none). */
101
+ let focusedIndex = $state(-1);
102
+ /** Set of message IDs that are manually collapsed in bounded mode. */
103
+ let collapsedIds = $state(new Set<string>());
104
+
105
+ function clampIndex(i: number): number {
106
+ return Math.max(0, Math.min(messages.length - 1, i));
107
+ }
108
+
109
+ function focusMessage(i: number) {
110
+ focusedIndex = clampIndex(i);
111
+ // Scroll focused message row into view
112
+ if (scrollEl) {
113
+ const row = scrollEl.querySelector<HTMLElement>(`[data-msg-idx="${focusedIndex}"]`);
114
+ row?.scrollIntoView({ block: "nearest", behavior: "smooth" });
115
+ }
116
+ }
117
+
118
+ function handleBoundedKeydown(e: KeyboardEvent) {
119
+ if (!scrollEl) return;
120
+ const h = scrollEl.clientHeight;
121
+ switch (e.key) {
122
+ case "j":
123
+ case "ArrowDown":
124
+ e.preventDefault();
125
+ if (focusedIndex < 0) focusMessage(0);
126
+ else focusMessage(focusedIndex + 1);
127
+ break;
128
+ case "k":
129
+ case "ArrowUp":
130
+ e.preventDefault();
131
+ if (focusedIndex < 0) focusMessage(messages.length - 1);
132
+ else focusMessage(focusedIndex - 1);
133
+ break;
134
+ case "Enter": {
135
+ e.preventDefault();
136
+ if (focusedIndex >= 0 && focusedIndex < messages.length) {
137
+ const id = messages[focusedIndex].id;
138
+ const next = new Set(collapsedIds);
139
+ if (next.has(id)) next.delete(id);
140
+ else next.add(id);
141
+ collapsedIds = next;
142
+ }
143
+ break;
144
+ }
145
+ case "PageUp": e.preventDefault(); scrollEl.scrollBy({ top: -h, behavior: "smooth" }); break;
146
+ case "PageDown": e.preventDefault(); scrollEl.scrollBy({ top: h, behavior: "smooth" }); break;
147
+ case "Home": e.preventDefault(); scrollEl.scrollTo({ top: 0, behavior: "smooth" }); focusMessage(0); break;
148
+ case "End": e.preventDefault(); scrollToBottom(); focusMessage(messages.length - 1); break;
149
+ }
150
+ }
151
+
152
+ // ── Markdown renderer ─────────────────────────────────────────────────────
153
+ // Safety: escape() is called on raw text BEFORE any regex replacements, so
154
+ // capture groups ($1 etc.) always contain already-escaped HTML entities.
155
+ // Injected HTML is limited to the known-safe tags below.
156
+ function escape(s: string): string {
157
+ return s
158
+ .replace(/&/g, "&amp;")
159
+ .replace(/</g, "&lt;")
160
+ .replace(/>/g, "&gt;");
161
+ }
162
+
163
+ function renderMarkdown(text: string): string {
164
+ // Split into code-block segments (captured) and normal segments
165
+ const parts = text.split(/(```[\s\S]*?```)/g);
166
+ return parts
167
+ .map((part, i) => {
168
+ if (i % 2 === 1) {
169
+ // Code block: strip fence markers, escape, wrap
170
+ const inner = escape(part.slice(3, -3).replace(/^\n/, "").replace(/\n$/, ""));
171
+ return `<pre class="md-pre"><code>${inner}</code></pre>`;
172
+ }
173
+ // Normal text: escape then apply inline markdown
174
+ let s = escape(part);
175
+ s = s.replace(/`([^`]+)`/g, '<code class="md-code">$1</code>');
176
+ s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
177
+ s = s.replace(/\*([^*\n]+)\*/g, "<em>$1</em>");
178
+ s = s.replace(/\n/g, "<br>");
179
+ return s;
180
+ })
181
+ .join("");
182
+ }
183
+
184
+ // ── Helpers ───────────────────────────────────────────────────────────────
185
+ function formatTime(date: Date): string {
186
+ return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
187
+ }
188
+ </script>
189
+
190
+ <div class="chat-view {className}" role="log" aria-live="polite" aria-label="Chat messages">
191
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
192
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
193
+ <div
194
+ class="chat-view__scroll"
195
+ bind:this={scrollEl}
196
+ tabindex="0"
197
+ role="region"
198
+ aria-label="Scrollable message history"
199
+ onscroll={handleScroll}
200
+ onkeydown={mode === "bounded" ? handleBoundedKeydown : handleKeydown}
201
+ >
202
+ <div class="chat-view__messages">
203
+ {#each messages as msg, i (msg.id)}
204
+ {#if mode === "bounded"}
205
+ <!-- ── Bounded mode: terminal-pane message box ── -->
206
+ <div
207
+ class="chat-view__box"
208
+ class:chat-view__box--user={msg.type === "user"}
209
+ class:chat-view__box--agent={msg.type === "agent" || !msg.type}
210
+ class:chat-view__box--system={msg.type === "system"}
211
+ class:chat-view__box--focused={focusedIndex === i}
212
+ role="article"
213
+ data-msg-idx={i}
214
+ onclick={() => { focusedIndex = i; }}
215
+ onkeydown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); focusedIndex = i; } }}
216
+ >
217
+ {#if msg.type === "system"}
218
+ <div class="chat-view__system">{msg.content}</div>
219
+ {:else}
220
+ <div class="chat-view__box-header">
221
+ <span class="chat-view__author">{msg.author}</span>
222
+ {#if showTimestamps}
223
+ <span class="chat-view__time">{formatTime(msg.timestamp)}</span>
224
+ {/if}
225
+ {#if collapsedIds.has(msg.id)}
226
+ <span class="chat-view__collapse-hint" aria-hidden="true">▸ collapsed</span>
227
+ {/if}
228
+ </div>
229
+ {#if msg.streaming}
230
+ <div class="chat-view__streaming" aria-label="Generating response">
231
+ <span class="chat-view__dot"></span>
232
+ <span class="chat-view__dot"></span>
233
+ <span class="chat-view__dot"></span>
234
+ </div>
235
+ {:else if collapsedIds.has(msg.id)}
236
+ <!-- collapsed: show nothing below header -->
237
+ {:else}
238
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
239
+ <div class="chat-view__content">{@html renderMarkdown(msg.content)}</div>
240
+ {/if}
241
+ {/if}
242
+ </div>
243
+ {:else}
244
+ <!-- ── Bubble mode: original layout ── -->
245
+ <div
246
+ class="chat-view__row"
247
+ class:chat-view__row--user={msg.type === "user"}
248
+ class:chat-view__row--agent={msg.type === "agent" || !msg.type}
249
+ class:chat-view__row--system={msg.type === "system"}
250
+ >
251
+ {#if msg.type === "system"}
252
+ <div class="chat-view__system">{msg.content}</div>
253
+ {:else}
254
+ <div
255
+ class="chat-view__bubble"
256
+ class:chat-view__bubble--user={msg.type === "user"}
257
+ class:chat-view__bubble--agent={msg.type === "agent" || !msg.type}
258
+ >
259
+ <div class="chat-view__bubble-header">
260
+ <span class="chat-view__author">{msg.author}</span>
261
+ {#if showTimestamps}
262
+ <span class="chat-view__time">{formatTime(msg.timestamp)}</span>
263
+ {/if}
264
+ </div>
265
+ {#if msg.streaming}
266
+ <div class="chat-view__streaming" aria-label="Generating response">
267
+ <span class="chat-view__dot"></span>
268
+ <span class="chat-view__dot"></span>
269
+ <span class="chat-view__dot"></span>
270
+ </div>
271
+ {:else}
272
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
273
+ <div class="chat-view__content">{@html renderMarkdown(msg.content)}</div>
274
+ {/if}
275
+ </div>
276
+ {/if}
277
+ </div>
278
+ {/if}
279
+ {/each}
280
+ <div class="chat-view__sentinel" aria-hidden="true"></div>
281
+ </div>
282
+ </div>
283
+
284
+ {#if newCount > 0 && !atBottom}
285
+ <button
286
+ class="chat-view__jump"
287
+ onclick={handleScrollToLatest}
288
+ aria-live="polite"
289
+ aria-label="Jump to latest messages"
290
+ >
291
+ ▼ {newCount} new {newCount === 1 ? "message" : "messages"}
292
+ </button>
293
+ {/if}
294
+ </div>
295
+
296
+ <style>
297
+ .chat-view {
298
+ position: relative;
299
+ display: flex;
300
+ flex-direction: column;
301
+ min-height: 0;
302
+ height: 100%;
303
+ }
304
+
305
+ .chat-view__scroll {
306
+ flex: 1;
307
+ min-height: 0;
308
+ overflow-y: auto;
309
+ scroll-behavior: smooth;
310
+ outline: none;
311
+ padding: var(--space-4, 16px);
312
+ display: flex;
313
+ flex-direction: column;
314
+ }
315
+
316
+ .chat-view__messages {
317
+ display: flex;
318
+ flex-direction: column;
319
+ gap: var(--space-3, 12px);
320
+ flex: 1;
321
+ justify-content: flex-end;
322
+ }
323
+
324
+ /* ── Row alignment ── */
325
+ .chat-view__row {
326
+ display: flex;
327
+ }
328
+
329
+ .chat-view__row--user { justify-content: flex-end; }
330
+ .chat-view__row--agent { justify-content: flex-start; }
331
+ .chat-view__row--system { justify-content: center; }
332
+
333
+ /* ── Bounded-mode message boxes ── */
334
+ .chat-view__box {
335
+ border: 1px solid var(--color-border, #2a2a2a);
336
+ border-radius: var(--radius-md, 8px);
337
+ overflow: hidden;
338
+ background: var(--surface-1, #141414);
339
+ cursor: default;
340
+ }
341
+
342
+ .chat-view__box--user {
343
+ border-color: var(--color-accent, #6366f1);
344
+ background: color-mix(in srgb, var(--color-accent, #6366f1) 8%, var(--surface-1, #141414));
345
+ }
346
+
347
+ .chat-view__box--system {
348
+ opacity: 0.7;
349
+ }
350
+
351
+ .chat-view__box--focused {
352
+ outline: 2px solid var(--color-accent, #6366f1);
353
+ outline-offset: -1px;
354
+ }
355
+
356
+ .chat-view__box-header {
357
+ display: flex;
358
+ align-items: center;
359
+ gap: var(--space-2, 8px);
360
+ padding: var(--space-2, 6px) var(--space-3, 12px);
361
+ border-bottom: 1px solid var(--color-border, #2a2a2a);
362
+ background: color-mix(in srgb, var(--color-border, #2a2a2a) 30%, transparent);
363
+ }
364
+
365
+ .chat-view__box--user .chat-view__box-header {
366
+ border-bottom-color: color-mix(in srgb, var(--color-accent, #6366f1) 40%, transparent);
367
+ }
368
+
369
+ .chat-view__collapse-hint {
370
+ margin-left: auto;
371
+ font-size: 10px;
372
+ color: var(--color-text-muted, #888);
373
+ font-family: var(--font-mono, monospace);
374
+ opacity: 0.7;
375
+ }
376
+
377
+ /* ── Bubbles ── */
378
+ .chat-view__bubble {
379
+ max-width: 75%;
380
+ border-radius: var(--radius-lg, 16px);
381
+ overflow: hidden;
382
+ display: flex;
383
+ flex-direction: column;
384
+ }
385
+
386
+ .chat-view__bubble--user {
387
+ background: var(--color-accent, #6366f1);
388
+ border-bottom-right-radius: var(--radius-sm, 6px);
389
+ }
390
+
391
+ .chat-view__bubble--agent {
392
+ background: var(--surface-2, #1e1e1e);
393
+ border: 1px solid var(--color-border, #2a2a2a);
394
+ border-bottom-left-radius: var(--radius-sm, 6px);
395
+ }
396
+
397
+ .chat-view__bubble-header {
398
+ display: flex;
399
+ align-items: center;
400
+ gap: var(--space-2, 8px);
401
+ padding: var(--space-2, 8px) var(--space-3, 12px) var(--space-1, 4px);
402
+ }
403
+
404
+ .chat-view__author {
405
+ font-size: var(--text-xs, 12px);
406
+ font-weight: 600;
407
+ color: var(--color-text, #e8e8e8);
408
+ opacity: 0.75;
409
+ }
410
+
411
+ .chat-view__box .chat-view__author {
412
+ color: var(--color-accent, #6366f1);
413
+ opacity: 1;
414
+ }
415
+
416
+ .chat-view__bubble--user .chat-view__author {
417
+ color: rgba(255, 255, 255, 0.85);
418
+ opacity: 1;
419
+ }
420
+
421
+ .chat-view__time {
422
+ font-size: 11px;
423
+ font-family: var(--font-mono, monospace);
424
+ color: var(--color-text-muted, #888);
425
+ opacity: 0.6;
426
+ }
427
+
428
+ .chat-view__bubble--user .chat-view__time {
429
+ color: rgba(255, 255, 255, 0.65);
430
+ }
431
+
432
+ /* ── Content ── */
433
+ .chat-view__content {
434
+ padding: var(--space-2, 8px) var(--space-3, 12px) var(--space-3, 12px);
435
+ font-size: var(--text-sm, 14px);
436
+ line-height: var(--leading-relaxed, 1.625);
437
+ word-break: break-word;
438
+ color: var(--color-text, #e8e8e8);
439
+ }
440
+
441
+ .chat-view__bubble--user .chat-view__content {
442
+ color: white;
443
+ }
444
+
445
+ /* Markdown elements */
446
+ .chat-view__content :global(strong) { font-weight: 700; }
447
+ .chat-view__content :global(em) { font-style: italic; }
448
+
449
+ .chat-view__content :global(code.md-code) {
450
+ background: rgba(0, 0, 0, 0.25);
451
+ border-radius: 4px;
452
+ padding: 0.1em 0.35em;
453
+ font-family: var(--font-mono, monospace);
454
+ font-size: 0.88em;
455
+ }
456
+
457
+ .chat-view__content :global(pre.md-pre) {
458
+ background: rgba(0, 0, 0, 0.3);
459
+ border-radius: var(--radius-sm, 6px);
460
+ padding: var(--space-3, 12px);
461
+ overflow-x: auto;
462
+ margin: var(--space-2, 8px) 0 0;
463
+ font-family: var(--font-mono, monospace);
464
+ font-size: 0.85em;
465
+ line-height: 1.5;
466
+ }
467
+
468
+ .chat-view__content :global(pre.md-pre code) {
469
+ background: none;
470
+ padding: 0;
471
+ border-radius: 0;
472
+ font-size: inherit;
473
+ }
474
+
475
+ /* ── System message ── */
476
+ .chat-view__system {
477
+ font-size: var(--text-xs, 12px);
478
+ color: var(--color-text-subtle, #555);
479
+ font-style: italic;
480
+ padding: 0 var(--space-3, 12px);
481
+ }
482
+
483
+ .chat-view__box--system .chat-view__system {
484
+ padding: var(--space-2, 8px) var(--space-3, 12px);
485
+ }
486
+
487
+ /* ── Streaming dots ── */
488
+ .chat-view__streaming {
489
+ padding: var(--space-3, 12px);
490
+ display: flex;
491
+ gap: 4px;
492
+ align-items: center;
493
+ }
494
+
495
+ .chat-view__dot {
496
+ display: inline-block;
497
+ width: 6px;
498
+ height: 6px;
499
+ border-radius: 50%;
500
+ background: var(--color-text-muted, #888);
501
+ animation: chat-dot-bounce 1.2s ease-in-out infinite;
502
+ }
503
+
504
+ .chat-view__dot:nth-child(2) { animation-delay: 0.2s; }
505
+ .chat-view__dot:nth-child(3) { animation-delay: 0.4s; }
506
+
507
+ @keyframes chat-dot-bounce {
508
+ 0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }
509
+ 40% { transform: translateY(-6px); opacity: 1; }
510
+ }
511
+
512
+ @media (prefers-reduced-motion: reduce) {
513
+ .chat-view__dot { animation: none; opacity: 0.7; }
514
+ }
515
+
516
+ /* ── Sentinel + jump button ── */
517
+ .chat-view__sentinel {
518
+ height: 1px;
519
+ flex-shrink: 0;
520
+ }
521
+
522
+ .chat-view__jump {
523
+ position: absolute;
524
+ bottom: var(--space-4, 16px);
525
+ left: 50%;
526
+ transform: translateX(-50%);
527
+ background: var(--color-accent, #6366f1);
528
+ color: white;
529
+ border: none;
530
+ border-radius: var(--radius-full, 9999px);
531
+ padding: 6px 16px;
532
+ font-size: var(--text-sm, 13px);
533
+ font-weight: 600;
534
+ cursor: pointer;
535
+ white-space: nowrap;
536
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
537
+ z-index: 10;
538
+ transition: opacity 0.15s;
539
+ }
540
+
541
+ .chat-view__jump:hover { opacity: 0.85; }
542
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { ChatViewMessage, ChatViewMode } from "./ChatView.types.js";
2
+ interface Props {
3
+ messages: ChatViewMessage[];
4
+ /**
5
+ * Layout mode.
6
+ * - "bounded": terminal-pane message boxes with keyboard navigation (default).
7
+ * - "bubble": classic chat bubble layout.
8
+ */
9
+ mode?: ChatViewMode;
10
+ /** Show author + timestamps in message headers. Default: true */
11
+ showTimestamps?: boolean;
12
+ /** Custom CSS class. */
13
+ class?: string;
14
+ /** Fired when user clicks the "jump to latest" indicator. */
15
+ onscrolltolatest?: () => void;
16
+ }
17
+ declare const ChatView: import("svelte").Component<Props, {}, "">;
18
+ type ChatView = ReturnType<typeof ChatView>;
19
+ export default ChatView;
@@ -0,0 +1 @@
1
+ export {};