@seed-ship/mcp-ui-solid 5.6.0 → 5.7.0
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.
- package/CHANGELOG.md +39 -0
- package/dist/components/UIResourceRenderer.cjs +40 -11
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts +36 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +40 -11
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/mcp-ui-spec/dist/schemas.cjs +9 -1
- package/dist/mcp-ui-spec/dist/schemas.cjs.map +1 -1
- package/dist/mcp-ui-spec/dist/schemas.js +9 -1
- package/dist/mcp-ui-spec/dist/schemas.js.map +1 -1
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.cts +26 -0
- package/dist/types.d.ts +26 -0
- package/docs/briefs/BRIEF-citations-in-table-cells.md +365 -0
- package/package.json +3 -3
- package/src/components/TableRenderer.citation.test.tsx +157 -0
- package/src/components/UIResourceRenderer.tsx +133 -12
- package/src/index.ts +5 -0
- package/src/types/index.ts +30 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -287,7 +287,98 @@ export function highlightQuery(html: string, query: string): string {
|
|
|
287
287
|
})
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Citation context — opt-in input to `renderCellValue` (v5.7.0).
|
|
292
|
+
*
|
|
293
|
+
* When passed, `[N]`, `Citation [N]`, `[CITATION N]` and `[📄 CITATION N]`
|
|
294
|
+
* markers in the cell text are normalized then replaced with chip HTML.
|
|
295
|
+
* Chips carry `data-citation-page`, `data-citation-doc`, and
|
|
296
|
+
* `data-citation-verified` attributes (already in the DOMPurify whitelist
|
|
297
|
+
* since v5.6.0) so a host's `target.closest('[data-citation-page]')`
|
|
298
|
+
* delegated click handler routes the click to the source-doc panel.
|
|
299
|
+
*
|
|
300
|
+
* See `mcp-ui-solid/docs/briefs/BRIEF-citations-in-table-cells.md`.
|
|
301
|
+
*/
|
|
302
|
+
export interface CitationCtx {
|
|
303
|
+
/**
|
|
304
|
+
* `Record<id, mapping>` keyed by the citation marker number (string-keyed
|
|
305
|
+
* because JSON serialization always produces strings; the runtime call
|
|
306
|
+
* sites accept either number or string ids and normalize internally).
|
|
307
|
+
*/
|
|
308
|
+
map: Record<string | number, { page: number | string; file?: string; file_id?: number | string }>
|
|
309
|
+
/**
|
|
310
|
+
* Optional override returning sanitized chip HTML for one marker. Wins
|
|
311
|
+
* over the default `defaultCitationChip` shape. Function inputs are
|
|
312
|
+
* intentionally `any`-loose so consumers can swap shapes (e.g. web
|
|
313
|
+
* citations vs doc citations) without subtyping the entry shape here.
|
|
314
|
+
*/
|
|
315
|
+
render?: (
|
|
316
|
+
id: number,
|
|
317
|
+
mapping: { page: number | string; file?: string; file_id?: number | string } | undefined
|
|
318
|
+
) => string
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Default chip HTML emitted by `transformCellCitations` when no
|
|
323
|
+
* `citationRender` override is supplied. Neutral Tailwind classes — hosts
|
|
324
|
+
* can override visual styling via the `.citation-ref` CSS class without
|
|
325
|
+
* passing a render override.
|
|
326
|
+
*/
|
|
327
|
+
function defaultCitationChip(
|
|
328
|
+
pageNum: number | string,
|
|
329
|
+
fileName: string,
|
|
330
|
+
verified = true
|
|
331
|
+
): string {
|
|
332
|
+
const safeDocName = encodeURIComponent(fileName || '')
|
|
333
|
+
const label = fileName ? `${fileName} - ${pageNum}` : `${pageNum}`
|
|
334
|
+
if (!verified) {
|
|
335
|
+
return `<span class="citation-ref inline-flex items-center gap-0.5 align-middle opacity-60"><span class="text-gray-500 line-through">[${label}]</span></span>`
|
|
336
|
+
}
|
|
337
|
+
return [
|
|
338
|
+
'<span class="citation-ref inline-flex items-center gap-0.5 align-middle">',
|
|
339
|
+
`<span class="text-gray-500">[${label}]</span>`,
|
|
340
|
+
'<button class="inline-flex items-center ml-0.5 px-1 py-0.5 text-xs bg-gray-800 hover:bg-gray-700 border border-gray-600 hover:border-teal-500 rounded cursor-pointer transition-colors align-middle"',
|
|
341
|
+
` data-citation-page="${pageNum}"`,
|
|
342
|
+
` data-citation-doc="${safeDocName}"`,
|
|
343
|
+
' data-citation-verified="true"',
|
|
344
|
+
` title="View source - ${label}">`,
|
|
345
|
+
'<svg class="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>',
|
|
346
|
+
'</button>',
|
|
347
|
+
'</span>',
|
|
348
|
+
].join('')
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Normalize bare `[N]`, `Citation [N]`, `[CITATION N]` markers to canonical
|
|
353
|
+
* `[📄 CITATION N]` then replace each canonical marker with chip HTML.
|
|
354
|
+
*
|
|
355
|
+
* Negative lookbehind `(?<![p.])` skips `[p.5]` (page form). Negative
|
|
356
|
+
* lookahead `(?!\()` skips `[text](url)` markdown links.
|
|
357
|
+
*/
|
|
358
|
+
function transformCellCitations(text: string, ctx: CitationCtx): string {
|
|
359
|
+
// 1. normalize bare [N] / Citation [N] / [CITATION N] → [📄 CITATION N]
|
|
360
|
+
let out = text.replace(/(?<![p.])\[(\d{1,2})\](?!\()/g, '[📄 CITATION $1]')
|
|
361
|
+
out = out.replace(/\bCitations?\s*\[(\d+)\]/gi, '[📄 CITATION $1]')
|
|
362
|
+
out = out.replace(/\[CITATION\s+(\d+)\]/gi, '[📄 CITATION $1]')
|
|
363
|
+
|
|
364
|
+
// 2. replace each canonical marker with chip HTML
|
|
365
|
+
return out.replace(
|
|
366
|
+
/[【[]\s*📄\s*CITATION\s*(\d+)\s*[】\]]/gi,
|
|
367
|
+
(_m, idStr) => {
|
|
368
|
+
const id = parseInt(idStr, 10)
|
|
369
|
+
const mapping = ctx.map[id] ?? ctx.map[String(id)]
|
|
370
|
+
if (ctx.render) return ctx.render(id, mapping)
|
|
371
|
+
if (mapping) return defaultCitationChip(mapping.page, mapping.file ?? '', true)
|
|
372
|
+
// Unresolved id: when the map is non-empty (consumer claims to know
|
|
373
|
+
// the citations), drop silently — it's likely an LLM hallucination.
|
|
374
|
+
// When the map is empty (consumer didn't supply one), preserve a
|
|
375
|
+
// human-visible placeholder so the marker isn't lost.
|
|
376
|
+
return Object.keys(ctx.map).length > 0 ? '' : `[réf. ${id}]`
|
|
377
|
+
}
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function renderCellValue(value: any, citationCtx?: CitationCtx): string {
|
|
291
382
|
// Handle null/undefined
|
|
292
383
|
if (value === null || value === undefined) {
|
|
293
384
|
return '-'
|
|
@@ -331,7 +422,9 @@ export function renderCellValue(value: any): string {
|
|
|
331
422
|
return '-'
|
|
332
423
|
}
|
|
333
424
|
|
|
334
|
-
// Detect and convert markdown links: [text](url)
|
|
425
|
+
// Detect and convert markdown links: [text](url) — runs FIRST because
|
|
426
|
+
// the citation transform's negative lookahead `(?!\()` would also skip
|
|
427
|
+
// these, but handling them here keeps the existing return path.
|
|
335
428
|
const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g
|
|
336
429
|
if (markdownLinkRegex.test(strValue)) {
|
|
337
430
|
// Replace all markdown links with HTML links
|
|
@@ -342,8 +435,36 @@ export function renderCellValue(value: any): string {
|
|
|
342
435
|
return DOMPurify.sanitize(htmlValue, { ADD_ATTR: ['target', 'rel'] })
|
|
343
436
|
}
|
|
344
437
|
|
|
438
|
+
// v5.7.0 — citation transform (opt-in). Replaces `[N]` style markers
|
|
439
|
+
// with chip HTML carrying `data-citation-*` attrs. Runs BEFORE the
|
|
440
|
+
// hasHtml / hasMarkdown branches so the resulting string flows through
|
|
441
|
+
// them naturally (chips are inline HTML; surviving markdown like
|
|
442
|
+
// **bold** is preserved by marked.parse since marked passes inline HTML
|
|
443
|
+
// through unchanged).
|
|
444
|
+
if (citationCtx) {
|
|
445
|
+
strValue = transformCellCitations(strValue, citationCtx)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Markdown markers WITHOUT square brackets — `[` and `]` were excluded
|
|
449
|
+
// because chip labels (`[Doc - 5]`) and unresolved-marker fallbacks
|
|
450
|
+
// (`[réf. 12]`) would otherwise force a marked.parse for cells that
|
|
451
|
+
// have no actual markdown. The hasMarkdown check ALSO runs before
|
|
452
|
+
// hasHtml so that mixed cells (`**bold** [1]` → `**bold** <chip>`)
|
|
453
|
+
// get marked first; marked preserves the inline chip HTML, then
|
|
454
|
+
// DOMPurify keeps the citation attrs via the extended whitelist.
|
|
455
|
+
const hasMarkdown = /[*_`#]/.test(strValue)
|
|
456
|
+
if (hasMarkdown) {
|
|
457
|
+
const parsed = marked.parse(strValue, { async: false }) as string
|
|
458
|
+
return DOMPurify.sanitize(parsed, {
|
|
459
|
+
ALLOWED_TAGS: ['a', 'strong', 'em', 'b', 'i', 'code', 'span', 'br', 'button', 'svg', 'path', 'p', 'ul', 'ol', 'li', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
|
|
460
|
+
ALLOWED_ATTR: ['href', 'target', 'rel', 'class', 'data-citation-page', 'data-citation-source', 'data-citation-doc', 'data-citation-verified', 'title', 'fill', 'stroke', 'viewBox', 'stroke-linecap', 'stroke-linejoin', 'stroke-width', 'd'],
|
|
461
|
+
ADD_ATTR: ['target', 'rel'],
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
|
|
345
465
|
// Detect raw HTML in cell values (e.g. <a href="..." data-citation-page="5">text</a>)
|
|
346
466
|
// This handles cases where cell data comes from innerHTML extraction
|
|
467
|
+
// OR where the citation transform above injected chip HTML.
|
|
347
468
|
const hasHtml = /<[a-z][\s\S]*>/i.test(strValue)
|
|
348
469
|
if (hasHtml) {
|
|
349
470
|
return DOMPurify.sanitize(strValue, {
|
|
@@ -353,14 +474,6 @@ export function renderCellValue(value: any): string {
|
|
|
353
474
|
})
|
|
354
475
|
}
|
|
355
476
|
|
|
356
|
-
// Check if value contains markdown formatting (bold, italic, code, etc.)
|
|
357
|
-
const hasMarkdown = /[*_`[\]#]/.test(strValue)
|
|
358
|
-
if (hasMarkdown) {
|
|
359
|
-
// Parse with marked and sanitize
|
|
360
|
-
const parsed = marked.parse(strValue, { async: false }) as string
|
|
361
|
-
return DOMPurify.sanitize(parsed, { ADD_ATTR: ['target', 'rel'] })
|
|
362
|
-
}
|
|
363
|
-
|
|
364
477
|
// Plain text — sanitize to prevent XSS via innerHTML
|
|
365
478
|
return DOMPurify.sanitize(strValue)
|
|
366
479
|
}
|
|
@@ -376,6 +489,14 @@ function TableRenderer(props: {
|
|
|
376
489
|
const tableParams = props.component.params as any
|
|
377
490
|
let scrollContainerRef: HTMLDivElement | undefined
|
|
378
491
|
|
|
492
|
+
// v5.7.0 — opt-in citation chip rendering inside cells. When `citationMap`
|
|
493
|
+
// is present in params, build a CitationCtx once and thread it through
|
|
494
|
+
// every `renderCellValue` call below. Absent → undefined → cells render
|
|
495
|
+
// as before (regression-safe).
|
|
496
|
+
const citationCtx: CitationCtx | undefined = tableParams.citationMap
|
|
497
|
+
? { map: tableParams.citationMap, render: tableParams.citationRender }
|
|
498
|
+
: undefined
|
|
499
|
+
|
|
379
500
|
// ─── Client-side sorting (v4.0.5) ────────────────────────
|
|
380
501
|
const allRows = () => tableParams.rows || []
|
|
381
502
|
const columns = () => tableParams.columns || []
|
|
@@ -638,7 +759,7 @@ function TableRenderer(props: {
|
|
|
638
759
|
<For each={tableParams.columns}>
|
|
639
760
|
{(column: any) => (
|
|
640
761
|
<td class="px-6 py-4 text-sm text-gray-700 dark:text-gray-200 whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
|
|
641
|
-
<div innerHTML={highlightQuery(renderCellValue(row[column.key]), debouncedQuery())} />
|
|
762
|
+
<div innerHTML={highlightQuery(renderCellValue(row[column.key], citationCtx), debouncedQuery())} />
|
|
642
763
|
</td>
|
|
643
764
|
)}
|
|
644
765
|
</For>
|
|
@@ -677,7 +798,7 @@ function TableRenderer(props: {
|
|
|
677
798
|
<For each={tableParams.columns}>
|
|
678
799
|
{(column: any) => (
|
|
679
800
|
<td class="px-6 py-4 text-sm text-gray-700 dark:text-gray-200 whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
|
|
680
|
-
<div innerHTML={highlightQuery(renderCellValue(row[column.key]), debouncedQuery())} />
|
|
801
|
+
<div innerHTML={highlightQuery(renderCellValue(row[column.key], citationCtx), debouncedQuery())} />
|
|
681
802
|
</td>
|
|
682
803
|
)}
|
|
683
804
|
</For>
|
package/src/index.ts
CHANGED
|
@@ -92,6 +92,11 @@ export type {
|
|
|
92
92
|
// Validation error mode (v5.4.0)
|
|
93
93
|
export type { ValidationErrorMode } from './components/UIResourceRenderer'
|
|
94
94
|
|
|
95
|
+
// Citation chips in table cells (v5.7.0 — brief: BRIEF-citations-in-table-cells.md)
|
|
96
|
+
export { renderCellValue } from './components/UIResourceRenderer'
|
|
97
|
+
export type { CitationCtx } from './components/UIResourceRenderer'
|
|
98
|
+
export type { CitationEntry } from './types'
|
|
99
|
+
|
|
95
100
|
// Runtime debug mode + perf marks (v5.4.0)
|
|
96
101
|
export { setDebugMode, isDebugEnabled } from './utils/logger'
|
|
97
102
|
export { markRenderStart, markRenderEnd, PERF_PREFIX } from './utils/perf'
|
package/src/types/index.ts
CHANGED
|
@@ -173,9 +173,21 @@ export interface TableVirtualizeOptions {
|
|
|
173
173
|
threshold?: number
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Citation map entry — source of a `[N]` citation marker rendered inline
|
|
178
|
+
* inside table cells (v5.7.0). See
|
|
179
|
+
* `mcp-ui-solid/docs/briefs/BRIEF-citations-in-table-cells.md`.
|
|
180
|
+
*/
|
|
181
|
+
export interface CitationEntry {
|
|
182
|
+
page: number | string
|
|
183
|
+
file?: string
|
|
184
|
+
file_id?: number | string
|
|
185
|
+
}
|
|
186
|
+
|
|
176
187
|
/**
|
|
177
188
|
* Table component parameters
|
|
178
189
|
* Updated Sprint Ultimate U.3: Added virtualization support
|
|
190
|
+
* Updated v5.7.0: Optional citationMap + citationRender for chip rendering
|
|
179
191
|
*/
|
|
180
192
|
export interface TableComponentParams {
|
|
181
193
|
title?: string
|
|
@@ -211,6 +223,24 @@ export interface TableComponentParams {
|
|
|
211
223
|
* Custom CSS class (Sprint 7)
|
|
212
224
|
*/
|
|
213
225
|
className?: string
|
|
226
|
+
/**
|
|
227
|
+
* Opt-in citation chip rendering (v5.7.0). Maps marker id (e.g. `1` from
|
|
228
|
+
* `[1]` or `[📄 CITATION 1]` in cell text) to its source. When set,
|
|
229
|
+
* `<TableRenderer>` replaces markers in cell strings with clickable
|
|
230
|
+
* chips carrying `data-citation-page` / `data-citation-doc` /
|
|
231
|
+
* `data-citation-verified` attributes that a host's delegated click
|
|
232
|
+
* handler can route. JSON-serializable — safe to send from MCP servers.
|
|
233
|
+
*/
|
|
234
|
+
citationMap?: Record<string | number, CitationEntry>
|
|
235
|
+
/**
|
|
236
|
+
* Optional override for the chip HTML (v5.7.0). When supplied, wins over
|
|
237
|
+
* the default chip shape. NOT JSON-serializable — must be wired by the
|
|
238
|
+
* consumer at render time, not from a server payload.
|
|
239
|
+
*/
|
|
240
|
+
citationRender?: (
|
|
241
|
+
id: number,
|
|
242
|
+
mapping: CitationEntry | undefined
|
|
243
|
+
) => string
|
|
214
244
|
}
|
|
215
245
|
|
|
216
246
|
/**
|