Conversation
Deploying ui with
|
| Latest commit: |
86cb250
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://af54ccd3.ui-6d0.pages.dev |
| Branch Preview URL: | https://superchat.ui-6d0.pages.dev |
There was a problem hiding this comment.
Pull request overview
Adds a new SuperChat feature module to @mieweb/ui: a multi-participant chat surface that composes the existing AI module and introduces a pluggable Markdown rendering pipeline with opt-in rich-render plugins (code/math/genui/mermaid/image/nitro-table). The PR also refactors AI Storybook stories into separate autodocs pages with shared fixtures, and introduces/updates maintainer/provider documentation to clarify repo conventions, submodules, and optional peer dependencies.
Changes:
- Introduces
SuperChat(component + types) andcreateMarkdownRendererwith a plugin registry and render-time context for streaming-aware nodes. - Adds opt-in rich Markdown plugins (code highlight + copy, KaTeX math, GenUI widgets, Mermaid, image lightbox, NITRO table) and corresponding tests/stories.
- Adds/updates provider documentation (
CONTRIBUTING.md, MAINTAINERS notes) and updates build configuration/peer deps to keep heavy dependencies optional and externalized.
Reviewed changes
Copilot reviewed 33 out of 34 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| tsup.config.ts | Adds tree-shakeable entry points for SuperChat and its plugin subpath; externalizes new optional deps. |
| superchat-plan.md | Design/architecture plan document for SuperChat participant model and render pipeline. |
| src/components/YChart/MAINTAINERS.md | Provider notes clarifying YChart is story-only and submodule-backed. |
| src/components/SuperChat/types.ts | Defines SuperChat participant + conversation/message model and render/plugin/GenUI contracts. |
| src/components/SuperChat/SuperChat.tsx | Implements the SuperChat shell: sidebar, thread, message rendering, composer with mention autocomplete. |
| src/components/SuperChat/SuperChat.test.tsx | Unit tests for Markdown rendering, sanitization, plugins, mention UX, send flow, and read-only mode. |
| src/components/SuperChat/SuperChat.stories.tsx | Autodocs stories demonstrating Markdown core and rich plugins, including a stateful host wrapper. |
| src/components/SuperChat/render/renderContext.ts | Render-time context for messageId/streaming used by custom Markdown nodes. |
| src/components/SuperChat/render/createMarkdownRenderer.tsx | Markdown renderer composer (GFM + sanitize) that merges plugin rehype/remark/components and sanitize schema contributions. |
| src/components/SuperChat/plugins/nitroTableGrid.tsx | Lazy-loaded NITRO grid wrapper for rendering parsed GFM tables via DataVis source/grid. |
| src/components/SuperChat/plugins/nitroTable.tsx | Plugin that overrides table nodes to render via lazy NITRO grid with HTML fallback/error boundary. |
| src/components/SuperChat/plugins/mermaid.tsx | Mermaid plugin: rehype transform + lazy render in strict mode with streaming gating and fallback. |
| src/components/SuperChat/plugins/math.tsx | KaTeX plugin: remark-math + rehype-katex and sanitize allow-list extensions. |
| src/components/SuperChat/plugins/index.ts | Public plugin subpath exports for opt-in rich rendering. |
| src/components/SuperChat/plugins/image.tsx | Image plugin: click-to-zoom using Messaging LightboxModal via portal. |
| src/components/SuperChat/plugins/genui.tsx | GenUI plugin: fenced JSON payload → host-registered lazy widgets with schema validation + prefetch policies. |
| src/components/SuperChat/plugins/code.tsx | Code plugin: rehype-highlight + copy-to-clipboard pre wrapper. |
| src/components/SuperChat/MAINTAINERS.md | Provider notes for SuperChat: decisions, bundle layout, plugin gotchas, and submodule/peer-dep behavior. |
| src/components/SuperChat/index.ts | Entry exports for SuperChat shell, renderer, context, and public types. |
| src/components/FloatingWindow/MAINTAINERS.md | Adds provider notes and invariants for FloatingWindow behavior and gestures. |
| src/components/ESheet/MAINTAINERS.md | Adds provider notes clarifying submodule-based implementation and build/test expectations. |
| src/components/DataVisNITRO/MAINTAINERS.md | Adds provider notes about NITRO architecture, deps, and “prefer NITRO over AGGrid”. |
| src/components/AI/storyData.ts | Extracts shared Storybook fixtures into a non-story module. |
| src/components/AI/MCPToolCall.stories.tsx | New focused autodocs story file for MCPToolCall using shared fixtures. |
| src/components/AI/MAINTAINERS.md | Updates provider notes with autodocs split + renderTextContent seam guidance. |
| src/components/AI/AIMessage.stories.tsx | New focused autodocs story file for AIMessage using shared fixtures. |
| src/components/AI/AIChat.stories.tsx | New focused autodocs story file for AIChat using shared fixtures. |
| src/components/AI/AI.stories.tsx | Removes the old combined multi-component story file. |
| src/components/AGGrid/MAINTAINERS.md | Adds/updates provider notes emphasizing NITRO preference and AGGrid invariants. |
| README.md | Replaces contributor workflow section with a pointer to CONTRIBUTING.md and a pnpm/submodule quick start. |
| package.json | Adds optional peer deps for markdown/math/mermaid rendering pipeline; adds dev deps for local builds/tests. |
| lessons/README.md | Clarifies lessons folder is consumer/adoption docs; points maintainers to CONTRIBUTING.md. |
| CONTRIBUTING.md | Adds a provider/maintainer guide: repo layout, commands, quality gates, autodocs conventions, entries/tree-shaking, submodules, optional peers. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@copilot resolve the merge conflicts in this pull request also fix any copilot comments. |
Done. I resolved the PR merge conflicts in commit |
|
Harmonize with this: #233 |
Add a native SuperChat module that composes the AI module's renderTextContent seam (Decisions 1-3 of superchat-plan.md): - Participant model (human/agent/system) generalizing AI + chat-component roles; chat-component-compatible conversation/thread/ref/linkBuilder shape - createMarkdownRenderer: composes render plugins into one renderTextContent; Markdown core (remark-gfm) with rehype-sanitize on untrusted output - Opt-in rich plugins behind a subpath: code (rehype-highlight + copy), math (KaTeX), genui (fenced JSON -> host-registered, lazy, schema-validated widget registry with component-vs-data prefetch) - SuperChat shell: sidebar/thread/composer, controlled props, timestamp-ordered interleaved replies, per-participant color/avatar cues, read-only - Composer @-mention menu (keyboard + mouse) and password-manager opt-out attrs - Stories (Markdown core / rich plugins / read-only), MAINTAINERS.md, 9 tests - Wire package.json optional peers + tsup subpath entries; not in base bundle
…plugins (Milestone 4) - mermaid: lazy-loaded strict-mode diagram rendering for fenced mermaid blocks - image: click-to-zoom Markdown images via Messaging LightboxModal (portaled to body) - nitro-table: GFM tables through DataVis NITRO grid, lazy-loaded, degrades to themed HTML table via GridErrorBoundary when datavis is unavailable - wire plugins into plugins/index.ts, tsup externals, and optional mermaid peer dep - add rich-plugins story + 3 tests (12 total), update MAINTAINERS.md
…ource + trust boundary
react-i18next → html-parse-stringify → void-elements live only in the pnpm virtual store, so Vite served void-elements raw without CJS→ESM default-export interop, breaking the datavis NITRO grid (GridErrorBoundary). Alias and force-include the chain in optimizeDeps, resolved against the workspace-local .pnpm/node_modules store.
… README A11y: landmark roles and accessible names on every structural region — group root, complementary sidebar, labelled main section, participants group, role=log live message thread, per-message article labels, and a proper mention combobox wired to its listbox. Add semantic data-slot hooks (header/thread/composer/bubble/meta/etc.) for styling and tests. Docs: new consumer README with a getting-started guide, a vocabulary glossary mapping each subcomponent term to its data-slot/role/name, an annotated visual-layout diagram, and cross-references to the AI, Messaging, and standalone chat-component surfaces. Point MAINTAINERS at it.
…ns, and SuperChatInbox
Break the monolithic SuperChat (sidebar + main panel) into three composable
components sharing one folder and one import path
(@mieweb/ui/components/SuperChat):
- SuperChat — single-conversation panel (takes one `conversation`);
root data-slot="superchat".
- SuperChatConversations — the conversation list (sidebar) with controlled/
uncontrolled selection; root data-slot="superchat-conversations".
- SuperChatInbox — wrapper composing the two; accepts the original full
SuperChatProps so it is a drop-in for the old component;
root data-slot="superchat-inbox".
Shared presentational pieces and helpers (ParticipantAvatar, ReferenceChip,
MessageRow, Composer, sidebarItem, time/mention utils) move to an internal
parts.tsx (not exported). No package.json/tsup change — the existing single
entry + ./components/* wildcard cover the new exports.
Stories: split per component (Panel / Conversations / Inbox) with shared
sample data in storyData.tsx; add args-driven Playground stories so the
Controls panel is populated. Tests: retarget panel tests to a single
conversation and add SuperChatConversations + SuperChatInbox coverage
(21 passing). Docs: README + MAINTAINERS remapped to the three-component
model and new data-slot vocabulary.
…tories - Add SuperChat.mdx Overview page that renders README.md verbatim (?raw), rewriting relative .md links to absolute GitHub URLs so they stay clickable in Storybook while remaining correct on GitHub/npm. - Trim duplicated component descriptions in the Panel/Inbox story metas to short blurbs that defer to the Overview (DRY). - Enable the full render-plugin set on the Playground stories so math/code/ GenUI/mermaid/image/table render; rename Panel 'MarkdownCore' -> 'CoreNoPlugins' (the lone plugin-less baseline, with an explanatory note). - Drop redundant stories (WithRichPlugins, Panel ReadOnly/Closable, Inbox Default/ReadOnly); keep Inbox Sources & Guards security reference.
… table contrast - Repoint code-badge and doc-link colors to injected --mieweb-primary-400 and hover backgrounds to --mieweb-muted (the previously referenced --mieweb-primary / --mieweb-accent tokens are never injected, so they always fell back to hardcoded blue/grey regardless of brand). - Add dark-mode prose-table rules for Markdown/README docs using injected --mieweb-foreground/--mieweb-card/--mieweb-border tokens. - Manager brand-theme switcher button now uses brand.primary so it reflects the active brand instead of the muted toolbar grey.
| // SuperChat (participant chips, unread badge, active/hover states) | ||
| 'bg-primary-100', | ||
| 'bg-primary-600', | ||
| 'hover:bg-primary-700', | ||
| 'dark:bg-primary-900/40', | ||
| 'text-primary-900', | ||
| 'dark:text-primary-100', | ||
| 'dark:text-primary-200', | ||
| 'hover:border-primary-300', | ||
| 'focus:border-primary-500', | ||
| 'focus:ring-primary-500', | ||
| 'text-[10px]', |
| it('keeps syntax-highlight token classes through sanitization (code plugin)', () => { | ||
| const r = createMarkdownRenderer({ plugins: [createCodePlugin()] }); | ||
| const { container } = renderText( | ||
| r('```js\nconst x = 1;\n```', { | ||
| messageId: 'm3', | ||
| streaming: false, | ||
| role: 'assistant', | ||
| }) | ||
| ); | ||
| expect( | ||
| container.querySelector('code.hljs, code[class*="language-"]') | ||
| ).not.toBeNull(); | ||
| }); | ||
| }); | ||
|
|
…kdown / plain) Every content message gets a margin copy control (left for incoming, right for own) that appears on hover/focus. The primary Copy writes both text/html and text/plain in one clipboard write so the paste target decides; explicit 'Copy as Markdown' and 'Copy as plain text' options are also offered.
… bottom On long messages the copy affordance now sticks to the bottom of the viewport (self-aligned to the message's end) so it stays reachable while scrolling and settles at the message bottom once fully in view. The menu opens upward to suit the low anchor.
| function baseSanitizeSchema(): SanitizeSchema { | ||
| const schema = JSON.parse(JSON.stringify(defaultSchema)) as SanitizeSchema; | ||
| const attributes = (schema.attributes ?? {}) as Record<string, unknown[]>; | ||
|
|
||
| const allowClass = (tag: string) => { | ||
| const existing = (attributes[tag] ?? []) as unknown[]; | ||
| // Drop any prior restricted className rule, allow className broadly. | ||
| const withoutClass = existing.filter( | ||
| (a) => !(Array.isArray(a) && a[0] === 'className') | ||
| ); | ||
| attributes[tag] = [...withoutClass, 'className']; | ||
| }; |
| void entry | ||
| .component() | ||
| .then((m) => { | ||
| if (active) setLoaded(() => m.default); | ||
| }) | ||
| .catch(() => { | ||
| if (active) | ||
| setValidated({ ok: false, message: 'Failed to load widget' }); | ||
| }); |
| const workspacePnpmVirtualNodeModulesDir = path.join( | ||
| rootNodeModulesDir, | ||
| '.pnpm/node_modules', | ||
| ); |
| const run = (fn: () => Promise<void>) => { | ||
| setOpen(false); | ||
| void fn().then(flash); | ||
| }; |
| const orderedThread = React.useMemo(() => { | ||
| const sorted = [...conversation.thread].sort(byTime); | ||
| return order === 'desc' ? sorted.reverse() : sorted; | ||
| }, [conversation, order]); |
| code: ({ node: _node, ...props }) => { | ||
| const isBlock = | ||
| typeof props.className === 'string' && | ||
| props.className.includes('language-'); | ||
| return ( | ||
| <code | ||
| {...props} | ||
| className={cn( | ||
| !isBlock && | ||
| 'rounded bg-neutral-200 px-1 py-0.5 text-[0.85em] dark:bg-neutral-700', | ||
| props.className | ||
| )} | ||
| /> | ||
| ); | ||
| }, |
| const writeText = async (value: string) => { | ||
| try { | ||
| await navigator.clipboard?.writeText(value); | ||
| } catch { | ||
| // Clipboard may be unavailable (insecure context / denied permission). | ||
| } | ||
| }; |
| code: ({ node: _node, ...props }) => { | ||
| const isBlock = | ||
| typeof props.className === 'string' && | ||
| props.className.includes('language-'); | ||
| return ( | ||
| <code | ||
| {...props} | ||
| className={cn( | ||
| !isBlock && | ||
| 'rounded bg-neutral-200 px-1 py-0.5 text-[0.85em] dark:bg-neutral-700', | ||
| props.className | ||
| )} | ||
| /> | ||
| ); | ||
| }, |
| function parsePayload(raw: string): AttachmentBlockPayload | null { | ||
| try { | ||
| const parsed = JSON.parse(raw) as Partial<AttachmentBlockPayload>; | ||
| if (!parsed || typeof parsed.type !== 'string') return null; | ||
| if (!parsed.id && !parsed.src) return null; | ||
| return { | ||
| id: typeof parsed.id === 'string' ? parsed.id : undefined, | ||
| type: parsed.type, | ||
| name: typeof parsed.name === 'string' ? parsed.name : 'attachment', | ||
| src: typeof parsed.src === 'string' ? parsed.src : undefined, | ||
| }; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } |
Summary
Adds SuperChat — a multi-participant (multi-human + multi-agent) chat component with a pluggable Markdown rendering pipeline — along with supporting maintainer docs and an AI stories refactor.
What's included
SuperChat component
SuperChatcomponent with multi-participant conversations,@-mention addressing of agents, a composer with mention autocomplete, and a read-only mode.createMarkdownRenderer) with a render-context for streaming state and a plugin registry.Render plugins
mermaidblocks (SVG trust boundary in strict mode).LightboxModal(portaled todocument.body).datavisis unavailable.All rich-render dependencies (react-markdown, remark/rehype plugins, katex, mermaid, datavis) are optional peer deps kept out of the base bundle; plugins are tree-shakeable via per-component tsup entries.
Docs & housekeeping
MAINTAINERS.mdfor SuperChat plus several other components (AGGrid, AI, DataVisNITRO, ESheet, FloatingWindow, YChart).AIChat,AIMessage,MCPToolCall) with sharedstoryData.superchat-plan.mddesign/plan document.Testing
@-mention menu, send flow, and read-only mode. All passing.Notes
datavisNITRO grid falls back to a themed HTML table in environments where thedatavissubmodule isn't checked out (or its deps fail to resolve) — this degradation is intentional and tested.Screenshots
Multi-participant conversation — humans + agents,

@-mention addressing, and Markdown (bold, lists, blockquotes).GenUI widget, reference chip & Mermaid diagram — interactive KPI card, a document reference, and a rendered

mermaidflowchart.Syntax-highlighted code & KaTeX math — fenced code with copy-to-clipboard and block/inline math.

NITRO table — GFM tables rendered through the DataVis NITRO pipeline (shown here degrading gracefully to a themed HTML table).

Image lightbox — click-to-zoom Markdown images via Messaging's

LightboxModal(portaled todocument.body).@-mention autocomplete — type

@in the composer to address a participant.Sources & Guards
A dedicated Sources & Guards Storybook story (
SuperChat → Sources & Guards) documents, per visual, the exact Markdown source that produces it and the guard (trust boundary) that keeps untrusted model/agent output safe. The rendered column uses the productioncreateMarkdownRendererwith every plugin enabled, so the doc stays in sync with the code.**bold**, lists,> quote,[link](url)rehype-sanitizeallow-list strips scripts/unknown tags; links forced totarget="_blank" rel="noopener noreferrer".rehype-highlight.hljs-*classes are explicitly allow-listed on code/pre/span; sanitize runs after highlight so only those classes survive.$…$/$$…$$math.tsxallow-lists KaTeX's HTML+MathML tags/attributes;rehype-katexruns withthrowOnError:false(malformed math degrades, never throws).genuiJSON blocks<genui-widget>tag is allow-listed. Unknown/invalid widgets degrade to an inert code block; mount + data fetch gated onstreaming.mermaidblockssecurityLevel:'strict'(labels sanitized, scripts stripped). The SVG bypassesrehype-sanitize, so strict mode is the trust boundary; gated onstreaming.src/altare already protocol-restricted byrehype-sanitize; the plugin only adds the zoom affordance and portalsLightboxModaltodocument.body.GridErrorBoundarydegrades to the themed HTML table ifdatavisis unavailable or the grid throws.The trust-boundary ordering (plugin rehype transforms → highlight/katex →
rehype-sanitizewith the merged allow-list) lives insrc/components/SuperChat/render/createMarkdownRenderer.tsx; thetrustedflag is the only way to skip sanitization, and only for host-authored content.