refactor(shared-ui): extract search-core for highlight + debounce

Both QuickInputBar (InputBar.svelte), CommandBar.svelte, and GlobalSpotlight
were duplicating syntax highlighting and the 150ms search debounce. Pull
these into a new `packages/shared-ui/src/search-core/` module so the two
input surfaces stay in sync on feel and matching rules.

- search-core/highlight.ts — HighlightPattern type, locale-aware
  getHighlightPatterns(), and the shared highlightText() (HTML-escape +
  span wrap). Patterns were previously in quick-input/highlightPatterns.ts
  + inline in CommandBar.svelte.
- search-core/config.ts — SEARCH_DEBOUNCE_MS = 150. Used from InputBar,
  CommandBar, GlobalSpotlight, and apps/mana web SearchEngine.
- quick-input/highlightPatterns.ts + types.ts become thin back-compat
  re-exports.
- Public surface: @mana/shared-ui now exports getHighlightPatterns,
  highlightText, SEARCH_DEBOUNCE_MS, and the HighlightPattern type.

No UX change. UIs still live in their own files (per earlier split
recommendation: shared backend, separate surfaces).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-15 01:06:37 +02:00
parent ad1659f036
commit 24eb8b3b7f
10 changed files with 161 additions and 204 deletions

View file

@ -1,29 +1,15 @@
<script lang="ts">
import { onMount } from 'svelte';
import { slide } from 'svelte/transition';
import type { QuickInputItem, CreatePreview, HighlightPattern } from './types';
import type { QuickInputItem, CreatePreview } from './types';
import InputBarContextMenu from './InputBarContextMenu.svelte';
import { getInputBarSettingsStore } from './inputBarSettings.svelte';
import { getHighlightPatterns } from './highlightPatterns';
import { getHighlightPatterns, highlightText, SEARCH_DEBOUNCE_MS } from '../search-core';
import type { HighlightPattern } from '../search-core';
// Settings store
const settingsStore = getInputBarSettingsStore();
function highlightText(text: string, patterns: HighlightPattern[]): string {
if (!text) return '';
let result = text;
// Escape HTML first
result = result.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
// Apply highlights (process in order, avoiding double-highlighting)
for (const { pattern, className } of patterns) {
result = result.replace(pattern, (match) => `<span class="${className}">${match}</span>`);
}
return result;
}
interface DefaultOption {
id: string;
label: string;
@ -242,7 +228,7 @@
} finally {
loading = false;
}
}, 150);
}, SEARCH_DEBOUNCE_MS);
}
async function triggerDeferredSearch() {
@ -669,25 +655,6 @@
}
}
.app-icon.success-icon {
color: hsl(var(--color-success, 142 71% 45%));
animation: success-check 0.4s ease-out;
}
@keyframes success-check {
0% {
transform: scale(0.5);
opacity: 0;
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
opacity: 1;
}
}
.left-action,
.right-action {
display: flex;
@ -696,13 +663,6 @@
flex-shrink: 0;
}
.app-icon {
width: 1.25rem;
height: 1.25rem;
color: hsl(var(--color-muted-foreground));
flex-shrink: 0;
}
.input-wrapper {
position: relative;
flex: 1;