Búsqueda global del Workspace (Fuse.js + índice estático)
Sustituida el 11-09-2026 por [[feature—workspace—busqueda-texto-completo]]: la cajita ya no usa Fuse.js ni el índice estático de 1,7 MB (que solo llevaba los primeros 1.500 caracteres de cada página); busca en el cuerpo entero con FTS5 en D1. Esta página queda como registro de cómo funcionaba.
Descripción
La búsqueda global del Workspace permite a los usuarios encontrar páginas wiki, secciones estáticas y entradas del worklog directamente desde el header de la aplicación. Antes del commit f13bf8a (2026-04-27), el componente buscaba sobre datos mock estáticos (tasks, news, worklog hardcodeados) y devolvía resultados incorrectos o vacíos.
Tras la corrección, la búsqueda opera sobre un índice real de 311 entradas generado en build-time, cubriendo:
| Fuente | Entradas | Notas |
|---|---|---|
| Wiki Supercontexto | ~285 | Páginas .md de src/content/wiki/ |
| Páginas Astro estáticas | 25 | Dashboard, Tareas, Biblioteca, Worklog, etc. |
| Worklog | ~1 por entrada | src/content/worklog/*.md |
Arquitectura
build-search-index.mjs
│
▼
public/search-index.json ← 311 entradas, ~484 KB
│
▼ lazy fetch al primer focus
SearchInline.tsx
│
├── Fuse.js (threshold 0.35, ignoreLocation: true)
│ ├── title (weight: 3)
│ ├── content (weight: 2)
│ └── section (weight: 1)
│
├── Historial en localStorage (clave: ws-search-history, máx: 6)
└── Keyboard nav: ↑ ↓ ⏎ Esc
Componente: SearchInline.tsx
Ubicación: src/components/shell/SearchInline.tsx
Tipo: React (forwardRef), montado en el shell del header global.
Estado
| Estado | Tipo | Descripción |
|---|---|---|
q | string | Query de búsqueda en curso |
results | SearchEntry[] | Resultados devueltos por Fuse |
history | string[] | Búsquedas recientes de localStorage |
sel | number | Índice del resultado seleccionado con teclado |
open | boolean | Visibilidad del dropdown |
Singletons de módulo
let fuseInstance: Fuse<SearchEntry> | null = null;
let indexLoaded = false;
El índice se carga una sola vez (lazy, al primer onFocus) y se reutiliza en toda la sesión. No hay re-fetch entre búsquedas.
Flujo de interacción
- Usuario hace focus →
loadIndex()fetcha/search-index.jsonsi aún no está cargado. - Usuario escribe →
useEffect([q])llama afuseInstance.search(term, { limit: 12 }). - Usuario selecciona con ↑↓/⏎ o click →
navigate(href)guarda en historial y redirige. - Sin query + historial disponible → dropdown muestra sección “Recientes” con últimas 6 búsquedas.
- Esc → cierra dropdown y elimina el focus del input.
Script: build-search-index.mjs
Ubicación: scripts/build-search-index.mjs
Ejecución: node scripts/build-search-index.mjs (manual o en CI pre-deploy)
Output: public/search-index.json
Estructura de cada entrada del índice
{
"title": "Modelo Rack — entity page",
"href": "/wiki/entity--racks--model--rack",
"section": "Wiki · Supercontexto",
"content": "<texto plano, primeros 1500 chars del body>"
}
Mapeo product → section
Valor product en frontmatter | Sección mostrada en UI |
|---|---|
crearack | Wiki · CreaRack Help |
crearack-tech | Wiki · CreaRack Tech |
workspace | Wiki · Workspace |
workspace-tech | Wiki · Workspace Tech |
ia-tech | Wiki · IA Tech |
supercontexto | Wiki · Supercontexto |
Proceso de indexación
- Carga las 25 páginas estáticas de
STATIC_PAGES(hardcodeadas en el script). - Itera
src/content/wiki/*.md— parsea frontmatter con regex propio, extraetitle,slug,product. - Itera
src/content/worklog/*.md— agrupa todos bajo/worklog. - Serializa el array completo a
public/search-index.json(sin pretty-print para minimizar tamaño). - Imprime resumen:
[search-index] N entradas → OUTPUT (estáticas: X, wiki: Y, worklog: Z).
Función extractFrontmatter
Mejorada respecto a la versión anterior: parsea todas las claves YAML de primer nivel (no solo title y slug) usando una regex de línea genérica, respetando comillas simples y dobles. Permite extraer product, status, type, etc.
Función stripMarkdown
Limpia el cuerpo del markdown antes de indexar: elimina bloques de código, imágenes, links, headers, énfasis, listas, tablas y blockquotes. El texto resultante se trunca a 1500 chars (wiki) o 2000 chars (worklog).
Estilos: globals.css
Cambios en la versión f13bf8a:
/* Dropdown: dimensiones ajustadas */
.search-inline-dropdown {
min-width: 360px; /* antes: sin min-width */
max-width: 520px; /* nuevo */
max-height: 480px; /* antes: 420px */
}
/* Nuevo: cabecera de sección dentro del dropdown */
.search-inline-section { ... }
/* Nuevo: botón "Limpiar" historial */
.search-inline-clear { ... }
Limitaciones conocidas
- El índice es estático (build-time). Páginas creadas o modificadas no aparecen en búsqueda hasta el próximo build + deploy.
- El fichero
search-index.jsonpesa ~484 KB — se carga completo en memoria del cliente en el primer focus. Aceptable para un tool interno de equipo pequeño; considerar particionado si crece >1 MB. - El worklog se indexa por entrada de fichero
.md, pero todas comparten la mismahref: /worklog(no deep-link a entrada individual). - Las páginas Astro estáticas están hardcodeadas en
STATIC_PAGES— añadir nuevas rutas requiere actualizar el script manualmente.
Historial de versiones
| Versión | Fecha | Descripción |
|---|---|---|
| v0 (pre-fix) | < 2026-04-27 | Búsqueda sobre mock estático, 1 entrada en índice, colecciones inexistentes |
| v1 (f13bf8a) | 2026-04-27 | Fuse.js real, 311 entradas, historial, keyboard nav |
Véase también
- [[workspace—que-es-workspace]]
- [[crearack—conceptos—interfaz-general]]