From 72ba7619804a7f5a66b294b8432d448dd11a39e0 Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 13 May 2026 07:54:05 +0000 Subject: [PATCH 1/4] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/link-assistant/react-chat-ui/issues/8 --- .gitkeep | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitkeep b/.gitkeep index 0c7d8e8..770d09d 100644 --- a/.gitkeep +++ b/.gitkeep @@ -1,2 +1,3 @@ # .gitkeep file auto-generated at 2026-04-14T12:50:58.126Z for PR creation at branch issue-36-780ff21d4079 for issue https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/36 -# Updated: 2026-05-12T15:33:00.819Z \ No newline at end of file +# Updated: 2026-05-12T15:33:00.819Z +# Updated: 2026-05-13T07:54:04.683Z \ No newline at end of file From 29f2f59e418183056b0b007a5f56a1dd760680fd Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 13 May 2026 08:24:52 +0000 Subject: [PATCH 2/4] Fix collapsed message bubbles and replace fake adapter previews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves issue #8. Outgoing chat bubbles no longer collapse on send because the adapter row now uses flexbox `row-reverse` instead of `direction: rtl`; hosted SDK profiles (Stream, Sendbird, CometChat, TalkJS, LiveChat) render through a new `HostedSourcePreview` that pairs the published import source with an offline transcript instead of the generic adapter. Profile scoring gains a renderer-derived live tier (A real local / B offline echo / C hosted source preview / D missing) that is surfaced in the comparison page along with emoji ✅/⚠️/❌ feature badges. The workspace is no longer boxed at 1480 px, and a `?debug=1` flag turns on verbose console output for the demo gallery and standalone profile pages. Adds the `REQUIREMENTS.md` traceability matrix and the `docs/case-studies/issue-8/` case study with before/after screenshots captured via Playwright. Bumps version to 0.12.0 and registers a changeset. --- .changeset/issue-8-real-demos.md | 15 ++ .gitignore | 3 + .prettierignore | 2 + REQUIREMENTS.md | 79 ++++++ docs/case-studies/issue-8/README.md | 237 ++++++++++++++++++ docs/case-studies/issue-8/data/issue-1.json | 1 + docs/case-studies/issue-8/data/issue-3.json | 1 + docs/case-studies/issue-8/data/issue-5.json | 1 + docs/case-studies/issue-8/data/issue-8.json | 1 + docs/case-studies/issue-8/data/pr-9.json | 1 + .../images/issue-8-failing-state-v2.png | Bin 0 -> 2240144 bytes .../issue-8/images/issue-8-failing-state.png | Bin 0 -> 2240144 bytes .../issue-8/images/issue-8-fixed-compare.png | Bin 0 -> 562809 bytes .../issue-8/images/issue-8-fixed-gallery.png | Bin 0 -> 502749 bytes .../images/issue-8-fixed-own-message.png | Bin 0 -> 505392 bytes .../images/issue-8-fixed-profile-page.png | Bin 0 -> 111502 bytes .../issue-8-fixed-stream-after-send.png | Bin 0 -> 513287 bytes .../images/issue-8-fixed-stream-hosted.png | Bin 0 -> 510950 bytes docs/chat-demos/src/debug.js | 38 +++ docs/chat-demos/src/demo-surfaces.jsx | 164 ++++++++---- docs/chat-demos/src/main.jsx | 153 ++++++++--- docs/chat-demos/src/profile-page.jsx | 11 +- docs/chat-demos/src/styles.css | 220 ++++++++++++++-- docs/screenshots/issue-5-chat-demos.png | Bin 260347 -> 479298 bytes eslint.config.js | 16 ++ package-lock.json | 21 +- package.json | 2 +- src/chat-demo-catalog.js | 57 +++-- src/extended-chat-catalog.js | 36 +-- src/index.js | 2 + src/profile-scoring.js | 59 ++++- tests/e2e/chat-demos.e2e.js | 2 +- 32 files changed, 965 insertions(+), 157 deletions(-) create mode 100644 .changeset/issue-8-real-demos.md create mode 100644 REQUIREMENTS.md create mode 100644 docs/case-studies/issue-8/README.md create mode 100644 docs/case-studies/issue-8/data/issue-1.json create mode 100644 docs/case-studies/issue-8/data/issue-3.json create mode 100644 docs/case-studies/issue-8/data/issue-5.json create mode 100644 docs/case-studies/issue-8/data/issue-8.json create mode 100644 docs/case-studies/issue-8/data/pr-9.json create mode 100644 docs/case-studies/issue-8/images/issue-8-failing-state-v2.png create mode 100644 docs/case-studies/issue-8/images/issue-8-failing-state.png create mode 100644 docs/case-studies/issue-8/images/issue-8-fixed-compare.png create mode 100644 docs/case-studies/issue-8/images/issue-8-fixed-gallery.png create mode 100644 docs/case-studies/issue-8/images/issue-8-fixed-own-message.png create mode 100644 docs/case-studies/issue-8/images/issue-8-fixed-profile-page.png create mode 100644 docs/case-studies/issue-8/images/issue-8-fixed-stream-after-send.png create mode 100644 docs/case-studies/issue-8/images/issue-8-fixed-stream-hosted.png create mode 100644 docs/chat-demos/src/debug.js diff --git a/.changeset/issue-8-real-demos.md b/.changeset/issue-8-real-demos.md new file mode 100644 index 0000000..82424b5 --- /dev/null +++ b/.changeset/issue-8-real-demos.md @@ -0,0 +1,15 @@ +--- +'@link-assistant/react-chat-ui': minor +--- + +Real demo surfaces, tiered scoring, and a readable comparison view (issue +#8). Replaces the generic `OfflineAdapterPreview` fallback for hosted SDK +profiles with a `HostedSourcePreview` that shows the published source +alongside an offline transcript, fixes the message-collapse bug by +swapping the `direction: rtl` outgoing layout for a flexbox row-reverse, +removes the boxed 1480 px workspace shell, and adds emoji ✅/⚠️/❌ feature +badges plus 🟢🟡🟠🔴 tier chips to the comparison page. Live tier is now +computed from `renderId` (A: real local, B: offline echo, C: hosted source +preview, D: not yet wired) and added to `scoreProfile` so ranking +differentiates real React surfaces from source previews. Adds a `?debug=1` +verbose mode for the demo gallery and standalone profile pages. diff --git a/.gitignore b/.gitignore index 852ef59..8a4a45e 100644 --- a/.gitignore +++ b/.gitignore @@ -147,3 +147,6 @@ docs/chat-demos/profiles/ # Ad-hoc experiments experiments/ +# Playwright MCP scratch snapshots +.playwright-mcp/ + diff --git a/.prettierignore b/.prettierignore index 114e416..c94bd38 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,3 +10,5 @@ docs/case-studies/*/data/ # Auto-generated micro-frontend profile pages docs/chat-demos/profiles/ experiments/ +# Playwright MCP scratch snapshots +.playwright-mcp/ diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md new file mode 100644 index 0000000..7ef9175 --- /dev/null +++ b/REQUIREMENTS.md @@ -0,0 +1,79 @@ +# Requirements + +This file consolidates every requirement raised in issues +[#1](https://github.com/link-assistant/react-chat-ui/issues/1), +[#3](https://github.com/link-assistant/react-chat-ui/issues/3), +[#5](https://github.com/link-assistant/react-chat-ui/issues/5), and the +currently active follow-up +[#8](https://github.com/link-assistant/react-chat-ui/issues/8). Every item +links the originating issue and the implementation surface in PR #9 so the +project can be measured against the user's intent end-to-end. + +Legend: + +- ✅ delivered in this branch (`issue-8-a2094a5fad53`) +- 🟡 partially delivered / planned for an incremental commit on this branch +- ❌ not yet attempted + +## 1. Gallery / surfaces + +| # | Requirement | Source | Status | Implementation reference | +| ---- | --------------------------------------------------------------------------------------------------- | -------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1.1 | Demo each popular React chat UI library with real imports, not a shared fake widget. | #1, #3, #5, #8 | 🟡 | `docs/chat-demos/src/demo-surfaces.jsx` now resolves dedicated surfaces for all libraries that can run client-side; hosted SDKs (Stream, Sendbird, CometChat, TalkJS, LiveChat) keep an explicit "Hosted SDK source" panel that shows the real published import path. | +| 1.2 | Each demo must render with the library's recommended theme and language switcher. | #1 | ✅ | Theme/locale state lives in `App` (`docs/chat-demos/src/main.jsx`) and is applied to every `DemoSurface`. | +| 1.3 | Inputs must be functional: typing and sending must mutate the visible thread. | #3, #8 | ✅ | The composer dispatches via `App.handleSubmit` which feeds the per-demo message store and shows the new bubble in the active surface. | +| 1.4 | Support markdown in messages AND in the input field. Multiple composer variants must be selectable. | #5 | ✅ | Three composers (`textarea`, `input`, `contenteditable`) wired in `Composer` with markdown rendered via `react-markdown` in every local surface. | +| 1.5 | Toggle avatars / display names / inline replies, similar to Telegram. | #5 | ✅ | `chatPresentation` toggles in the header drive `showAvatars`, `showNames`, `showReplies` props on every surface. | +| 1.6 | Each demo must have its own micro-frontend page so dependencies do not collide. | #5 | ✅ | `docs/chat-demos/src/profile-page.jsx` renders the per-profile page; Vite builds one entry per profile. | +| 1.7 | Each demo must be reachable as a separate item in the sidebar (including our own chat UI). | #5 | ✅ | The sidebar in `App` lists every profile and the own-chat profile sits alongside library profiles. | +| 1.8 | Include our own React chat UI (re-implemented from GPTutor). | #5 | ✅ | `chatscopeCommunity` is replaced by the bespoke own-chat surface that lives in the repo. | +| 1.9 | Investigate and integrate the T3 chat component if a public library exists. | #5 | 🟡 | Research recorded in `docs/case-studies/issue-8/data/research-notes.md`. T3 chat is not published as a standalone library on npm, so it is excluded with a documented note. | +| 1.10 | Expand the catalog to 10–20 of the most popular React chat solutions. | #5 | ✅ | Catalog covers 16 profiles across `src/chat-demo-catalog.js` and `src/extended-chat-catalog.js`. | + +## 2. Comparison / benchmark / scoring + +| # | Requirement | Source | Status | Implementation reference | +| --- | ---------------------------------------------------------------------------------------------------------------------- | ------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2.1 | Show a serious comparison of features, limitations, lock-in across every library. | #3, #5 | ✅ | `CompareView` in `docs/chat-demos/src/main.jsx` renders a per-profile feature matrix, limitations, and lock-in chips. | +| 2.2 | Use color/emoji to make the comparison readable at a glance. | #8 | ✅ | Feature cells render ✅ / ⚠️ / ❌; tiers render a colored chip (A 🟢, B 🟡, C 🟠, D 🔴). | +| 2.3 | Show measured rendering performance (time + DOM nodes). | #3 | ✅ | React Profiler captures `actualDuration` and DOM node counts and they appear in the benchmark column. | +| 2.4 | Show approximate memory usage per demo. | #3 | ✅ | `performance.memory.usedJSHeapSize` is sampled when available; missing on Firefox/Safari, flagged with `n/a`. | +| 2.5 | Show source-code cost per library (lines of code + character count) so we can compare developer cost. | #3 | ✅ | `chatDemoSources` reports `linesOfCode` and `characters` for each profile; the compare table surfaces both. | +| 2.6 | Show GitHub stars, last release time, license. | #3 | ✅ | `chatDemoCatalog` records `repository`, `licenseId`, `stars`, `lastReleaseAt` (refreshed from the GitHub API at build time when credentials are available; otherwise the last successful snapshot is used). | +| 2.7 | Rank libraries with a scoring algorithm that surfaces feature-rich, actively-maintained, configurable ones at the top. | #5 | ✅ | `src/profile-scoring.js` blends feature breadth, surface support, recency, popularity, and a tiered live-render bonus (A 12 / B 8 / C 4 / D 0). | +| 2.8 | The benchmark must not collapse to a binary "real or fake" multiplier. | #8 | ✅ | The live bonus tier is determined by surface category (local renderer, offline echo, hosted source, source only) and gives partial credit. | + +## 3. UI / UX + +| # | Requirement | Source | Status | Implementation reference | +| --- | ------------------------------------------------------------------------------------------ | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 3.1 | Fix message markup collapse on send: new bubbles must render correctly. | #8 | ✅ | `direction: rtl` removed from `.adapter-message.is-local` in `docs/chat-demos/src/styles.css`; we now use `flex-direction: row-reverse` on a flex row so author + bubble keep their natural box. | +| 3.2 | Use the full screen width; do not show a narrow boxed gallery surrounded by whitespace. | #8 | ✅ | `.workspace` no longer caps width or applies a card shadow; sidebar and content share the viewport via CSS grid. | +| 3.3 | Provide a theme switcher (light + dark) and a language switcher. | #1 | ✅ | Implemented in the header (`App`) with `data-theme`/`data-locale` attributes propagated to surfaces. | +| 3.4 | Quality bar: e2e tests must verify viewing a message and sending a message for every demo. | #5 | ✅ | `tests/e2e/chat-demos.e2e.js` walks every profile, sends a message, asserts the new bubble appears, and takes a screenshot. | +| 3.5 | Generate screenshots automatically (browser-commander). | #1 | ✅ | The e2e suite stores screenshots under `tests/e2e/screenshots/.png`. | + +## 4. Data store + +| # | Requirement | Source | Status | Implementation reference | +| --- | ------------------------------------------------------------------------------- | ------ | ------ | ---------------------------------------------------------------------------------------------------------------------------- | +| 4.1 | Persist messages with the Doublets database (`doublets-rs` via `doublets-web`). | #1 | ✅ | `src/doublets-store.js` exposes `createDoubletsUnicodeStore` and is plugged into the App's message reducer. | +| 4.2 | Store unicode strings safely (link-cli style). | #1 | ✅ | Unicode store uses UTF-32 codepoint links per the link-cli pattern; covered by unit tests in `tests/doublets-store.test.js`. | + +## 5. Engineering hygiene + +| # | Requirement | Source | Status | Implementation reference | +| --- | --------------------------------------------------------------------------------------------------------------------------- | -------------- | ------ | -------------------------------------------------------------------------------------------------------------- | +| 5.1 | Compile a case study under `docs/case-studies/issue-{id}` with timeline, requirements, root causes, and proposed solutions. | #1, #3, #5, #8 | ✅ | `docs/case-studies/issue-8/README.md`. | +| 5.2 | Search online and record external facts in the case study. | #3 | ✅ | `docs/case-studies/issue-8/data/research-notes.md`. | +| 5.3 | Add verbose / debug logging that is off by default. | #3 | ✅ | `?debug=1` query param turns on `console.debug`-style logs from `src/debug.js`; off by default. | +| 5.4 | File upstream issues against third-party projects when a bug or missing capability is found. | #3 | 🟡 | None filed yet; reproduction repros are tracked under `docs/case-studies/issue-8/data/upstream-candidates.md`. | +| 5.5 | Plan and execute everything in a single PR (PR #9) on branch `issue-8-a2094a5fad53`. | #8 | ✅ | All commits land on `issue-8-a2094a5fad53` and PR #9 is updated, not replaced. | +| 5.6 | Maintain `REQUIREMENTS.md` capturing every user requirement. | #8 | ✅ | This file. | + +## Traceability matrix (summary) + +- Issue #1 → §1.2, §1.6, §1.7, §3.3, §3.5, §4.1, §4.2, §5.1 +- Issue #3 → §1.1, §1.3, §2.1, §2.3, §2.4, §2.5, §2.6, §5.2, §5.3, §5.4 +- Issue #5 → §1.1, §1.4, §1.5, §1.6, §1.7, §1.8, §1.9, §1.10, §2.1, §2.7, §3.4, §3.5 +- Issue #8 → §1.1, §2.2, §2.8, §3.1, §3.2, §5.5, §5.6 diff --git a/docs/case-studies/issue-8/README.md b/docs/case-studies/issue-8/README.md new file mode 100644 index 0000000..ab1bc3a --- /dev/null +++ b/docs/case-studies/issue-8/README.md @@ -0,0 +1,237 @@ +# Case Study: Issue #8 - Demos, comparison, and scoring still look fake + +- **Issue:** [#8](https://github.com/link-assistant/react-chat-ui/issues/8) +- **Pull request:** [#9](https://github.com/link-assistant/react-chat-ui/pull/9) +- **Date opened:** 2026-05-13 +- **Status:** Addressed in PR #9 on branch `issue-8-a2094a5fad53` + +## Executive Summary + +Issue #8 is a follow-up on issues #1, #3, and #5. The previous deliverables +shipped a gallery, profile pages, e2e suite, and a Doublets-backed unicode +store, but a hands-on review found four real defects: + +1. **Sending a new reply collapses the message markup** in the active + integration preview. The new "local" bubble renders as a 1-2 character + wide vertical column instead of a balanced chat bubble. +2. **12 of 16 profiles render a generic `OfflineAdapterPreview`.** That + shared adapter is essentially the same fake UI for hosted SDKs (Stream, + Sendbird, CometChat, TalkJS, LiveChat) and runtime SDKs (assistant-ui, + nlux, Vercel AI SDK, MinChat, react-simple-chatbot, react-chatbot-kit, + Rocket.Chat Fuselage). The user's complaint is correct: there is no real + library code in the gallery for those profiles. +3. **The "benchmark / scoring" is half synthetic.** Render time, DOM nodes, + and heap are real React Profiler values, but the ranking weight relies on + a binary `liveBonus` (12 points if the profile uses one of four hard-coded + renderer IDs). For libraries that do not run locally, the benchmark numbers + are effectively identical because they all hit the same fake adapter. +4. **The compare page is hard to read.** It shows `"Yes"` / `"No"` literals + in 12 feature columns and unstyled limitation / lock-in lists. There is no + color or emoji encoding. + +In addition the user pointed out the gallery is presented as a 1480 px wide +"card" inside a sea of whitespace, which wastes screen real estate, and +"Ranked libraries" can render with a stale ordering when the own-chat profile +is filtered into a different section. + +## Captured Evidence + +| File | Purpose | +| -------------------------------------------- | ------------------------------------------------------------------- | +| `data/issue-8.json` | Fresh issue #8 metadata (title, body, labels, created/updated). | +| `data/issue-1.json` | Original "research best React chats" issue. | +| `data/issue-3.json` | "Our UI examples look fake, input is not working" predecessor. | +| `data/issue-5.json` | "Fixes and improvements" predecessor with the comparison list ask. | +| `data/pr-9.json` | PR #9 metadata snapshot (draft, WIP body). | +| `images/issue-8-failing-state.png` | Screenshot embedded in issue #8 showing the collapse defect. | +| `images/issue-8-fixed-gallery.png` | Fixed gallery: full-width workspace, tier chips, no boxed shell. | +| `images/issue-8-fixed-own-message.png` | New local reply renders as a full bubble (no collapse). | +| `images/issue-8-fixed-compare.png` | Comparison page with emoji ✅/⚠️/❌ matrix and 🟢🟡🟠🔴 tier chips. | +| `images/issue-8-fixed-stream-hosted.png` | Stream profile via HostedSourcePreview (real source + transcript). | +| `images/issue-8-fixed-stream-after-send.png` | Stream hosted preview after sending a reply (no collapse). | +| `images/issue-8-fixed-profile-page.png` | Standalone profile-page route renders the same fix. | +| `data/profile-rendering-audit.md` | Table mapping every profile to its renderer (real vs offline fake). | +| `data/research-notes.md` | Online research on each library's React entry point and benchmarks. | + +The screenshot was verified by checking the PNG magic bytes +(`89 50 4E 47 0D 0A 1A 0A`) before visual inspection. + +## Timeline + +- **2026-04-** Issue #1 asks for visual demos and screenshots of the best + React chat UIs. +- **2026-04-** Issue #3 reports that the demos look fake and the composer + input is broken. The case study under `docs/case-studies/issue-3` and the + first version of the gallery (PR not in this branch) addressed that. +- **2026-05-12** Issue #5 lands and asks for the own chat to be a separate + entry, e2e tests on every demo, sorted libraries, and the comparison view. + PR #6/#7 (already merged) addressed that. Case study in + `docs/case-studies/issue-5`. +- **2026-05-13 07:50 UTC** Issue #8 opens with a screenshot showing the + collapse defect, the fake adapter problem, the unreadable comparison, and + the boxed layout complaint. PR #9 is opened in WIP state on the same day. + +## Root Cause Analysis + +### Defect 1 - "Local" bubble collapses on send + +`docs/chat-demos/src/styles.css:732-738` declares for outgoing bubbles: + +```css +.adapter-message.is-local { + justify-self: end; + grid-template-columns: minmax(0, 1fr) 36px; + direction: rtl; +} + +.adapter-message.is-local > * { + direction: ltr; +} +``` + +The `direction: rtl` reverses the grid track placement so the avatar lands on +the right. But the `justify-self: end` on the outer message plus the +`max-width: 760px` on `.adapter-message` collapses the grid item: the +`minmax(0, 1fr)` track receives 0 px from the parent because the parent is +already content-sized in the RTL flow, leaving only the avatar's 36 px to +fill. Result: the bubble shrinks to the width of its content (the longest +unbreakable token, like a single character) which renders as the vertical +slice in the screenshot. + +The same pattern in `.own-chat-message.is-local` would produce the same +defect, but the own-chat surface has `display: block` content with explicit +`grid-template-columns: minmax(0, 1fr) auto` and no `justify-self: end`, +so it is less affected. + +**Fix:** replace `direction: rtl` with a real flexbox approach +(`flex-direction: row-reverse` for the row and `align-self: flex-end` for the +parent) and let the bubble itself use `max-width` rather than collapsing. + +### Defect 2 - The same fake adapter is used for 12 of 16 profiles + +`docs/chat-demos/src/demo-surfaces.jsx:309-348` falls back to +`OfflineAdapterPreview` for every rendererId other than `chatscope`, +`react-chat-elements`, `deep-chat`, and `own-chat`. The fallback renders the +adapter's own DOM rather than the real library, so all hosted SDKs and most +runtime SDKs look identical. + +**Fix:** add real local surfaces for every library whose React entry point can +mount without external services. Concretely: + +- `@minchat/react-chat-ui` - mounts purely client-side. +- `@nlux/react` - accepts an `adapter` prop; we can provide an in-memory + echo adapter. +- `react-simple-chatbot` - takes a static `steps` array. +- `react-chatbot-kit` - takes a static `config` + parser + provider. +- `@rocket.chat/fuselage` - primitives, no service. +- `ai` (Vercel AI SDK) - `useChat` can be initialized with a static initial + message list, no API call required for the static preview. +- `@assistant-ui/react` - exposes the primitives and an `useLocalRuntime` + hook that does not require an external runtime. + +Hosted SDKs that legitimately cannot run without credentials (`stream-chat-react`, +`@sendbird/uikit-react`, `@cometchat/chat-uikit-react`, `@talkjs/react`, +`@livechat/widget-react`) keep a clearly labelled "Hosted SDK source" panel +with the actual `import` and a small live preview rendered through the same +component (where the React component is exported separately from the network +adapter), and the source block remains the source of truth. + +### Defect 3 - Scoring uses a binary live bonus + +`src/profile-scoring.js:8-13` keeps a hard-coded `LIVE_RENDERER_IDS` set, and +the bonus is 12 points if the renderer is one of them, 0 otherwise. There +is no granularity for "renders locally with no service" vs "renders with a +hosted backend". We also do not feed measured render-time or DOM-node counts +into the ranking - those are display-only. + +**Fix:** keep the scoring deterministic (so tests are stable) but split the +live bonus into tiers: + +- Tier A (12 pts): local renderer with real package + working composer. +- Tier B (8 pts): local renderer that uses an offline adapter (echo / local + runtime). +- Tier C (4 pts): hosted SDK with a non-interactive but real React surface. +- Tier D (0 pts): source-only credential-gated. + +And expose the actual measured render-time, heap, DOM nodes in the comparison +table, so the user can compare libraries on observable numbers, not opinions. + +### Defect 4 - Compare page is unreadable + +`docs/chat-demos/src/main.jsx:357-435` renders feature support as the +strings `"Yes"` and `"No"` in plain table cells, and limitations / lock-ins +as small `