@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,630 @@
1
+ <!--
2
+ SemanticSearchInput — IntelliSense-style autocomplete with local embeddings.
3
+
4
+ Features:
5
+ - Multi-mode search: default semantic, prefix # tag, @ entity, / command
6
+ - Ghost text for top suggestion (Tab to accept, like GitHub Copilot)
7
+ - Rich suggestion items: category badge, relative timestamp, similarity bar,
8
+ snippet preview on hover / keyboard focus
9
+ - Result grouping by category with section headers
10
+ - Full keyboard navigation: ↑↓ move selection, Tab accept ghost, Enter select,
11
+ Escape dismiss
12
+ - ARIA combobox pattern (role="combobox" + role="listbox")
13
+ -->
14
+ <script lang="ts">
15
+ import type { SearchMode, SemanticSearchResult } from "./SemanticSearchInput.types.js";
16
+
17
+ interface Props {
18
+ /** Called after debounce with the trimmed query and detected mode. */
19
+ onquery: (query: string, mode: SearchMode) => Promise<SemanticSearchResult[]>;
20
+ /** Called when the user selects a suggestion. */
21
+ onselect: (result: SemanticSearchResult) => void;
22
+ placeholder?: string;
23
+ /** Debounce delay in ms before firing onquery. Default: 100 */
24
+ debounceMs?: number;
25
+ /** Maximum suggestions to show. Default: 8 */
26
+ maxSuggestions?: number;
27
+ /** Show top suggestion as ghost text. Default: true */
28
+ showGhostText?: boolean;
29
+ /** Which modes to enable. Default: all */
30
+ modes?: SearchMode[];
31
+ class?: string;
32
+ }
33
+
34
+ let {
35
+ onquery,
36
+ onselect,
37
+ placeholder = "Search…",
38
+ debounceMs = 100,
39
+ maxSuggestions = 8,
40
+ showGhostText = true,
41
+ modes = ["semantic", "tag", "entity", "command"],
42
+ class: className = "",
43
+ }: Props = $props();
44
+
45
+ let inputEl: HTMLInputElement | undefined = $state();
46
+ let value = $state("");
47
+ let isFocused = $state(false);
48
+ let results: SemanticSearchResult[] = $state([]);
49
+ let selectedIndex = $state(-1);
50
+ let showDropdown = $state(false);
51
+ let activeSnippetId = $state<string | null>(null);
52
+
53
+ // Unique IDs for ARIA combobox → listbox relationship
54
+ function createListboxId() {
55
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
56
+ return `ssi-listbox-${crypto.randomUUID()}`;
57
+ }
58
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
59
+ const array = new Uint32Array(1);
60
+ crypto.getRandomValues(array);
61
+ return `ssi-listbox-${array[0].toString(36)}`;
62
+ }
63
+ return `ssi-listbox-${Date.now().toString(36)}`;
64
+ }
65
+
66
+ const listboxId = createListboxId();
67
+
68
+ let debounceTimer: ReturnType<typeof setTimeout> | undefined;
69
+
70
+ // ── Mode detection ───────────────────────────────────────────────────────────
71
+
72
+ const mode = $derived.by((): SearchMode => {
73
+ if (value.startsWith("#") && modes.includes("tag")) return "tag";
74
+ if (value.startsWith("@") && modes.includes("entity")) return "entity";
75
+ if (value.startsWith("/") && modes.includes("command")) return "command";
76
+ return "semantic";
77
+ });
78
+
79
+ // Strip the prefix before handing the query to the callback
80
+ const cleanQuery = $derived(mode === "semantic" ? value : value.slice(1));
81
+
82
+ // ── Ghost text ───────────────────────────────────────────────────────────────
83
+
84
+ const ghostSuffix = $derived.by(() => {
85
+ if (!showGhostText || !value || results.length === 0) return "";
86
+ const top = results[0].text;
87
+ // Compare against cleanQuery so the mode prefix (# @ /) is not included in
88
+ // the startsWith check, but the ghost suffix is appended after the full value.
89
+ if (top.toLowerCase().startsWith(cleanQuery.toLowerCase())) {
90
+ return top.slice(cleanQuery.length);
91
+ }
92
+ return "";
93
+ });
94
+
95
+ // ── Result grouping ──────────────────────────────────────────────────────────
96
+
97
+ const groupedResults = $derived.by(() => {
98
+ const groups = new Map<string, SemanticSearchResult[]>();
99
+ for (const r of results) {
100
+ const cat = r.category || "other";
101
+ if (!groups.has(cat)) groups.set(cat, []);
102
+ groups.get(cat)!.push(r);
103
+ }
104
+ return Array.from(groups.entries()).map(([cat, items]) => ({ cat, items }));
105
+ });
106
+
107
+ // ── Search & selection ───────────────────────────────────────────────────────
108
+
109
+ async function triggerSearch(query: string, searchMode: SearchMode) {
110
+ const trimmedQuery = query.trim();
111
+ if (!trimmedQuery) {
112
+ results = [];
113
+ showDropdown = false;
114
+ selectedIndex = -1;
115
+ return;
116
+ }
117
+ try {
118
+ const raw = await onquery(trimmedQuery, searchMode);
119
+ results = raw.slice(0, maxSuggestions);
120
+ selectedIndex = -1;
121
+ showDropdown = results.length > 0;
122
+ } catch {
123
+ results = [];
124
+ showDropdown = false;
125
+ }
126
+ }
127
+
128
+ function selectResult(item: SemanticSearchResult) {
129
+ value = item.text;
130
+ results = [];
131
+ showDropdown = false;
132
+ selectedIndex = -1;
133
+ activeSnippetId = null;
134
+ onselect(item);
135
+ }
136
+
137
+ // ── Event handlers ───────────────────────────────────────────────────────────
138
+
139
+ function handleInput(e: Event) {
140
+ const target = e.target as HTMLInputElement;
141
+ value = target.value;
142
+ clearTimeout(debounceTimer);
143
+ debounceTimer = setTimeout(() => triggerSearch(cleanQuery, mode), debounceMs);
144
+ }
145
+
146
+ function handleKeydown(e: KeyboardEvent) {
147
+ if (e.key === "ArrowDown") {
148
+ e.preventDefault();
149
+ if (results.length > 0) selectedIndex = Math.min(selectedIndex + 1, results.length - 1);
150
+ } else if (e.key === "ArrowUp") {
151
+ e.preventDefault();
152
+ if (results.length > 0) selectedIndex = Math.max(selectedIndex - 1, -1);
153
+ } else if (e.key === "Tab") {
154
+ if (ghostSuffix) {
155
+ e.preventDefault();
156
+ value = value + ghostSuffix;
157
+ clearTimeout(debounceTimer);
158
+ debounceTimer = setTimeout(() => triggerSearch(cleanQuery, mode), debounceMs);
159
+ }
160
+ } else if (e.key === "Enter") {
161
+ e.preventDefault();
162
+ if (selectedIndex >= 0 && selectedIndex < results.length) {
163
+ selectResult(results[selectedIndex]);
164
+ }
165
+ } else if (e.key === "Escape") {
166
+ results = [];
167
+ showDropdown = false;
168
+ selectedIndex = -1;
169
+ activeSnippetId = null;
170
+ }
171
+ }
172
+
173
+ function handleFocus() {
174
+ isFocused = true;
175
+ if (value.trim() && results.length > 0) showDropdown = true;
176
+ }
177
+
178
+ function handleBlur() {
179
+ isFocused = false;
180
+ // Delay hiding so a mousedown on a dropdown item fires first
181
+ setTimeout(() => {
182
+ showDropdown = false;
183
+ }, 150);
184
+ }
185
+
186
+ // ── GUI helpers ──────────────────────────────────────────────────────────────
187
+
188
+ /** Split result text into matched / unmatched parts for highlight rendering. */
189
+ function getHighlightParts(text: string, query: string): Array<{ text: string; match: boolean }> {
190
+ if (!query.trim()) return [{ text, match: false }];
191
+ const lowerText = text.toLowerCase();
192
+ const lowerQuery = query.trim().toLowerCase();
193
+ const idx = lowerText.indexOf(lowerQuery);
194
+ if (idx < 0) return [{ text, match: false }];
195
+ return [
196
+ { text: text.slice(0, idx), match: false },
197
+ { text: text.slice(idx, idx + lowerQuery.length), match: true },
198
+ { text: text.slice(idx + lowerQuery.length), match: false },
199
+ ];
200
+ }
201
+
202
+ /** Format a Date as a human-readable relative time string. */
203
+ function relativeTime(date: Date): string {
204
+ const diff = Date.now() - date.getTime();
205
+ const secs = Math.floor(diff / 1000);
206
+ if (secs < 60) return "just now";
207
+ const mins = Math.floor(secs / 60);
208
+ if (mins < 60) return `${mins}m ago`;
209
+ const hours = Math.floor(mins / 60);
210
+ if (hours < 24) return `${hours}h ago`;
211
+ const days = Math.floor(hours / 24);
212
+ if (days < 30) return `${days}d ago`;
213
+ const months = Math.floor(days / 30);
214
+ if (months < 12) return `${months}mo ago`;
215
+ return `${Math.floor(months / 12)}y ago`;
216
+ }
217
+ </script>
218
+
219
+ <div class="ssi {className}">
220
+ <!-- ── Input field ──────────────────────────────────────────────────────── -->
221
+ <div class="ssi__field" class:ssi__field--focused={isFocused}>
222
+ <span class="ssi__icon" aria-hidden="true">⌕</span>
223
+
224
+ <div class="ssi__input-wrap">
225
+ <!-- Ghost text overlay: shows the typed value (transparent) + ghost suffix (dim) -->
226
+ {#if ghostSuffix}
227
+ <div class="ssi__ghost-layer" aria-hidden="true">
228
+ <span class="ssi__ghost-typed">{value}</span><span class="ssi__ghost-suffix">{ghostSuffix}</span>
229
+ </div>
230
+ {/if}
231
+ <input
232
+ bind:this={inputEl}
233
+ class="ssi__input"
234
+ class:ssi__input--has-ghost={!!ghostSuffix}
235
+ type="text"
236
+ {value}
237
+ {placeholder}
238
+ aria-label="Search"
239
+ role="combobox"
240
+ aria-controls={listboxId}
241
+ aria-expanded={showDropdown}
242
+ aria-autocomplete="list"
243
+ aria-haspopup="listbox"
244
+ oninput={handleInput}
245
+ onkeydown={handleKeydown}
246
+ onfocus={handleFocus}
247
+ onblur={handleBlur}
248
+ />
249
+ </div>
250
+
251
+ {#if mode !== "semantic"}
252
+ <span class="ssi__mode-badge ssi__mode-badge--{mode}" aria-hidden="true">
253
+ {mode === "tag" ? "#" : mode === "entity" ? "@" : "/"}
254
+ </span>
255
+ {/if}
256
+ </div>
257
+
258
+ <!-- ── Dropdown ────────────────────────────────────────────────────────── -->
259
+ {#if showDropdown && results.length > 0}
260
+ <!-- svelte-ignore a11y_no_redundant_roles -->
261
+ <ul id={listboxId} class="ssi__dropdown" role="listbox" aria-label="Search suggestions">
262
+ {#each groupedResults as group}
263
+ <li class="ssi__group-header" role="presentation">
264
+ <span class="ssi__group-label">{group.cat}</span>
265
+ <span class="ssi__group-count">{group.items.length}</span>
266
+ </li>
267
+ {#each group.items as item}
268
+ {@const flatIndex = results.indexOf(item)}
269
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
270
+ <li
271
+ class="ssi__option"
272
+ class:ssi__option--selected={flatIndex === selectedIndex}
273
+ role="option"
274
+ aria-selected={flatIndex === selectedIndex}
275
+ tabindex="-1"
276
+ onmousedown={() => selectResult(item)}
277
+ onmouseenter={() => {
278
+ selectedIndex = flatIndex;
279
+ activeSnippetId = item.id;
280
+ }}
281
+ onmouseleave={() => {
282
+ if (activeSnippetId === item.id) activeSnippetId = null;
283
+ }}
284
+ >
285
+ <div class="ssi__option-body">
286
+ <div class="ssi__option-main">
287
+ {#if item.icon}
288
+ <span class="ssi__option-icon" aria-hidden="true">{item.icon}</span>
289
+ {/if}
290
+ <span class="ssi__option-text">
291
+ {#each getHighlightParts(item.text, cleanQuery) as part}
292
+ {#if part.match}
293
+ <mark class="ssi__highlight">{part.text}</mark>
294
+ {:else}
295
+ {part.text}
296
+ {/if}
297
+ {/each}
298
+ </span>
299
+ </div>
300
+ <div class="ssi__option-meta">
301
+ <span class="ssi__category-badge ssi__category-badge--{item.category}">
302
+ {item.category}
303
+ </span>
304
+ <span class="ssi__time">{relativeTime(item.timestamp)}</span>
305
+ <div
306
+ class="ssi__sim-bar"
307
+ title="{(item.similarity * 100).toFixed(0)}% match"
308
+ aria-label="{(item.similarity * 100).toFixed(0)}% similarity"
309
+ >
310
+ <div class="ssi__sim-fill" style:width="{item.similarity * 100}%"></div>
311
+ </div>
312
+ </div>
313
+ </div>
314
+ {#if (flatIndex === selectedIndex || activeSnippetId === item.id) && item.snippet}
315
+ <div class="ssi__snippet">{item.snippet}</div>
316
+ {/if}
317
+ </li>
318
+ {/each}
319
+ {/each}
320
+ </ul>
321
+ {/if}
322
+ </div>
323
+
324
+ <style>
325
+ /* ── Wrapper ─────────────────────────────────────────────────────────────── */
326
+
327
+ .ssi {
328
+ position: relative;
329
+ width: 100%;
330
+ }
331
+
332
+ /* ── Input field ─────────────────────────────────────────────────────────── */
333
+
334
+ .ssi__field {
335
+ display: flex;
336
+ align-items: center;
337
+ width: 100%;
338
+ box-sizing: border-box;
339
+ background: var(--surface-2, #1e1e1e);
340
+ border: 1.5px solid var(--color-border, #2a2a2a);
341
+ border-radius: var(--radius-md, 10px);
342
+ padding: 0 var(--space-3, 12px);
343
+ transition: border-color 0.15s ease;
344
+ gap: var(--space-2, 8px);
345
+ }
346
+
347
+ .ssi__field--focused {
348
+ border-color: var(--color-accent, #6366f1);
349
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent, #6366f1) 20%, transparent);
350
+ }
351
+
352
+ .ssi__icon {
353
+ color: var(--color-text-muted, #888888);
354
+ font-size: var(--text-base, 16px);
355
+ flex-shrink: 0;
356
+ user-select: none;
357
+ }
358
+
359
+ /* ── Input wrap + ghost text ─────────────────────────────────────────────── */
360
+
361
+ .ssi__input-wrap {
362
+ position: relative;
363
+ flex: 1;
364
+ min-width: 0;
365
+ display: flex;
366
+ align-items: center;
367
+ }
368
+
369
+ /* Ghost overlay: absolutely positioned behind the real input */
370
+ .ssi__ghost-layer {
371
+ position: absolute;
372
+ inset: 0;
373
+ display: flex;
374
+ align-items: center;
375
+ pointer-events: none;
376
+ overflow: hidden;
377
+ white-space: pre;
378
+ font-family: inherit;
379
+ font-size: var(--text-base, 16px);
380
+ line-height: var(--leading-normal, 1.5);
381
+ }
382
+
383
+ /* The typed portion is invisible (matches input's real text) */
384
+ .ssi__ghost-typed {
385
+ visibility: hidden;
386
+ white-space: pre;
387
+ }
388
+
389
+ /* The ghost suffix is dim */
390
+ .ssi__ghost-suffix {
391
+ color: var(--color-text-subtle, #555);
392
+ white-space: pre;
393
+ }
394
+
395
+ .ssi__input {
396
+ flex: 1;
397
+ min-width: 0;
398
+ padding: var(--space-3, 12px) 0;
399
+ background: transparent;
400
+ border: none;
401
+ outline: none;
402
+ color: var(--color-text, #e8e8e8);
403
+ font-family: inherit;
404
+ font-size: var(--text-base, 16px);
405
+ line-height: var(--leading-normal, 1.5);
406
+ position: relative;
407
+ z-index: 1;
408
+ }
409
+
410
+ .ssi__input::placeholder {
411
+ color: var(--color-text-muted, #888888);
412
+ }
413
+
414
+ /* ── Mode badge ──────────────────────────────────────────────────────────── */
415
+
416
+ .ssi__mode-badge {
417
+ flex-shrink: 0;
418
+ font-size: 11px;
419
+ font-weight: 700;
420
+ letter-spacing: 0.05em;
421
+ text-transform: uppercase;
422
+ border-radius: var(--radius-full, 9999px);
423
+ padding: 2px 7px;
424
+ white-space: nowrap;
425
+ background: color-mix(in srgb, var(--color-accent, #6366f1) 18%, transparent);
426
+ color: var(--color-accent-hover, #818cf8);
427
+ }
428
+
429
+ .ssi__mode-badge--tag {
430
+ background: color-mix(in srgb, var(--color-success, #22c55e) 18%, transparent);
431
+ color: var(--color-success, #22c55e);
432
+ }
433
+
434
+ .ssi__mode-badge--entity {
435
+ background: color-mix(in srgb, var(--color-warning, #f59e0b) 18%, transparent);
436
+ color: var(--color-warning, #f59e0b);
437
+ }
438
+
439
+ .ssi__mode-badge--command {
440
+ background: color-mix(in srgb, var(--color-info, #3b82f6) 18%, transparent);
441
+ color: var(--color-info, #3b82f6);
442
+ }
443
+
444
+ /* ── Dropdown ────────────────────────────────────────────────────────────── */
445
+
446
+ .ssi__dropdown {
447
+ position: absolute;
448
+ top: calc(100% + var(--space-1, 4px));
449
+ left: 0;
450
+ right: 0;
451
+ z-index: 100;
452
+ margin: 0;
453
+ padding: var(--space-1, 4px) 0;
454
+ list-style: none;
455
+ background: var(--surface-2, #1e1e1e);
456
+ border: 1.5px solid var(--color-border, #2a2a2a);
457
+ border-radius: var(--radius-md, 10px);
458
+ box-shadow: var(--shadow-lg, 0 10px 25px rgba(0, 0, 0, 0.2));
459
+ overflow: hidden;
460
+ }
461
+
462
+ /* ── Group header ────────────────────────────────────────────────────────── */
463
+
464
+ .ssi__group-header {
465
+ display: flex;
466
+ align-items: center;
467
+ justify-content: space-between;
468
+ padding: var(--space-1, 4px) var(--space-3, 12px);
469
+ margin-top: var(--space-1, 4px);
470
+ border-top: 1px solid var(--color-border, #2a2a2a);
471
+ pointer-events: none;
472
+ }
473
+
474
+ .ssi__group-header:first-child {
475
+ border-top: none;
476
+ margin-top: 0;
477
+ }
478
+
479
+ .ssi__group-label {
480
+ font-size: 10px;
481
+ font-weight: 700;
482
+ text-transform: uppercase;
483
+ letter-spacing: 0.08em;
484
+ color: var(--color-text-subtle, #555);
485
+ }
486
+
487
+ .ssi__group-count {
488
+ font-size: 10px;
489
+ color: var(--color-text-subtle, #555);
490
+ font-variant-numeric: tabular-nums;
491
+ }
492
+
493
+ /* ── Option ──────────────────────────────────────────────────────────────── */
494
+
495
+ .ssi__option {
496
+ padding: var(--space-2, 8px) var(--space-3, 12px);
497
+ cursor: pointer;
498
+ font-size: var(--text-sm, 14px);
499
+ color: var(--color-text, #e8e8e8);
500
+ transition: background 0.1s ease;
501
+ }
502
+
503
+ .ssi__option:hover,
504
+ .ssi__option--selected {
505
+ background: color-mix(in srgb, var(--color-accent, #6366f1) 10%, transparent);
506
+ }
507
+
508
+ .ssi__option-body {
509
+ display: flex;
510
+ align-items: center;
511
+ gap: var(--space-2, 8px);
512
+ }
513
+
514
+ .ssi__option-main {
515
+ display: flex;
516
+ align-items: center;
517
+ gap: var(--space-1, 4px);
518
+ flex: 1;
519
+ min-width: 0;
520
+ overflow: hidden;
521
+ }
522
+
523
+ .ssi__option-icon {
524
+ flex-shrink: 0;
525
+ font-size: var(--text-base, 16px);
526
+ line-height: 1;
527
+ }
528
+
529
+ .ssi__option-text {
530
+ overflow: hidden;
531
+ text-overflow: ellipsis;
532
+ white-space: nowrap;
533
+ }
534
+
535
+ .ssi__highlight {
536
+ background: transparent;
537
+ color: var(--color-accent-hover, #818cf8);
538
+ font-weight: 700;
539
+ }
540
+
541
+ /* ── Option meta row ─────────────────────────────────────────────────────── */
542
+
543
+ .ssi__option-meta {
544
+ display: flex;
545
+ align-items: center;
546
+ gap: var(--space-2, 8px);
547
+ flex-shrink: 0;
548
+ }
549
+
550
+ .ssi__category-badge {
551
+ font-size: 10px;
552
+ font-weight: 700;
553
+ letter-spacing: 0.05em;
554
+ text-transform: uppercase;
555
+ border-radius: var(--radius-full, 9999px);
556
+ padding: 1px 6px;
557
+ white-space: nowrap;
558
+ background: color-mix(in srgb, var(--color-text-muted, #888) 18%, transparent);
559
+ color: var(--color-text-muted, #888);
560
+ }
561
+
562
+ .ssi__category-badge--fact {
563
+ background: color-mix(in srgb, var(--color-info, #3b82f6) 18%, transparent);
564
+ color: var(--color-info, #3b82f6);
565
+ }
566
+
567
+ .ssi__category-badge--preference {
568
+ background: color-mix(in srgb, var(--color-success, #22c55e) 18%, transparent);
569
+ color: var(--color-success, #22c55e);
570
+ }
571
+
572
+ .ssi__category-badge--decision {
573
+ background: color-mix(in srgb, var(--color-accent, #6366f1) 18%, transparent);
574
+ color: var(--color-accent-hover, #818cf8);
575
+ }
576
+
577
+ .ssi__category-badge--conversation {
578
+ background: color-mix(in srgb, var(--color-warning, #f59e0b) 18%, transparent);
579
+ color: var(--color-warning, #f59e0b);
580
+ }
581
+
582
+ .ssi__category-badge--command {
583
+ background: color-mix(in srgb, var(--color-info, #3b82f6) 18%, transparent);
584
+ color: var(--color-info, #3b82f6);
585
+ }
586
+
587
+ .ssi__time {
588
+ font-size: 11px;
589
+ color: var(--color-text-subtle, #555);
590
+ font-family: var(--font-mono, monospace);
591
+ white-space: nowrap;
592
+ }
593
+
594
+ /* ── Similarity bar ──────────────────────────────────────────────────────── */
595
+
596
+ .ssi__sim-bar {
597
+ width: 40px;
598
+ height: 4px;
599
+ background: var(--color-border, #2a2a2a);
600
+ border-radius: var(--radius-full, 9999px);
601
+ overflow: hidden;
602
+ flex-shrink: 0;
603
+ }
604
+
605
+ .ssi__sim-fill {
606
+ height: 100%;
607
+ background: var(--color-accent, #6366f1);
608
+ border-radius: var(--radius-full, 9999px);
609
+ transition: width 0.2s ease;
610
+ }
611
+
612
+ .ssi__option--selected .ssi__sim-fill {
613
+ background: var(--color-accent-hover, #818cf8);
614
+ }
615
+
616
+ /* ── Snippet preview ─────────────────────────────────────────────────────── */
617
+
618
+ .ssi__snippet {
619
+ margin-top: var(--space-1, 4px);
620
+ padding: var(--space-1, 4px) var(--space-2, 8px);
621
+ background: color-mix(in srgb, var(--color-text-subtle, #555) 8%, transparent);
622
+ border-left: 2px solid var(--color-accent, #6366f1);
623
+ border-radius: 0 var(--radius-sm, 6px) var(--radius-sm, 6px) 0;
624
+ font-size: var(--text-xs, 12px);
625
+ color: var(--color-text-muted, #888);
626
+ line-height: var(--leading-relaxed, 1.625);
627
+ white-space: pre-wrap;
628
+ word-break: break-word;
629
+ }
630
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { SearchMode, SemanticSearchResult } from "./SemanticSearchInput.types.js";
2
+ interface Props {
3
+ /** Called after debounce with the trimmed query and detected mode. */
4
+ onquery: (query: string, mode: SearchMode) => Promise<SemanticSearchResult[]>;
5
+ /** Called when the user selects a suggestion. */
6
+ onselect: (result: SemanticSearchResult) => void;
7
+ placeholder?: string;
8
+ /** Debounce delay in ms before firing onquery. Default: 100 */
9
+ debounceMs?: number;
10
+ /** Maximum suggestions to show. Default: 8 */
11
+ maxSuggestions?: number;
12
+ /** Show top suggestion as ghost text. Default: true */
13
+ showGhostText?: boolean;
14
+ /** Which modes to enable. Default: all */
15
+ modes?: SearchMode[];
16
+ class?: string;
17
+ }
18
+ declare const SemanticSearchInput: import("svelte").Component<Props, {}, "">;
19
+ type SemanticSearchInput = ReturnType<typeof SemanticSearchInput>;
20
+ export default SemanticSearchInput;
@@ -0,0 +1 @@
1
+ export {};