* feat: open Mermaid diagrams in the artifact panel with SVG and PNG export
Mermaid diagrams previously rendered inline only, and the artifact panel
routed every artifact through Sandpack even when no bundler was needed.
- Route Mermaid artifacts to a direct renderer in ArtifactTabs, moving the
Sandpack path into a lazily loaded SandboxArtifactTabs so opening a
diagram no longer pulls in the bundler chrome or the startup config.
- Add an inline artifact card that opens the diagram in the panel instead
of rendering the same diagram twice.
- Add SVG and PNG export from both the inline diagram and the panel
header, with size-capped canvas scaling and background compositing.
- Lazy-load the artifact panel in Presentation and ShareArtifacts.
- Accessibility: label the panel as a dialog on mobile with a focus trap,
make the mobile resize handle keyboard operable, restore focus to the
opener on close, and honor prefers-reduced-motion.
- Fix the generated Sandpack wrapper to serialize diagram source instead
of interpolating it into a template literal.
- Cover the new paths with unit tests and a cross-browser Playwright spec.
* fix: keep Mermaid artifact identity and render state per diagram
Addresses three review findings on the Mermaid artifact panel.
Mermaid fences do not consume a code-block index, so every diagram in a
message received the same `mermaid-${blockIndex}` and therefore the same
Recoil artifact key: expanding one overwrote the other, and both cards
read as selected. Mermaid fences now carry their own index sequence,
seeded per markdown block the same way the code and artifact counters
are, so the id stays stable across streamed tokens.
The panel renderer is keyed by artifact id, so switching directly
between two diagrams can no longer carry the previous render, its
dimensions, or its export payload across the boundary while the new
source debounces. Editing an open diagram still does not remount.
The preview Refresh action drives the Sandpack client, which a Mermaid
preview never populates, so it only covered the panel with a spinner.
It is hidden for Mermaid, which offers its own retry on render failure.
Also drops com_ui_mermaid_export_preparing and com_ui_mermaid_source,
which no longer have call sites, fixing the unused-i18n-keys check.
* fix: bind Mermaid preview and export to the artifact on screen
Three further review findings, all on state outliving what it describes.
The editor reset in ArtifactTabs only lands after commit, so the render
that switched artifacts still passed the previous artifact's editor text
to the freshly keyed renderer, which mounted showing (and exporting) the
diagram just navigated away from. Editor text is now ignored until the
reset catches up. SandboxArtifactTabs carried the same pattern and gets
the same guard.
Switching to the code tab unmounts the preview, but the export payload
survived it, so the toolbar kept exporting a diagram that was no longer
on screen and no longer matched an edited source. The renderer now
withdraws its payload on unmount, and the export action is scoped to the
preview tab.
The diagram canvas mounts only once there is a diagram to show, so the
ResizeObserver ran against a null ref while the placeholder was up and
never saw the real element. Wide diagrams were fitted to the default
700px and clipped in narrower panels. Observation now re-runs when the
canvas appears.
* fix: scope Mermaid artifact ids to the content part
Each content part renders its own markdown tree, so the per-message
Mermaid counter restarts at zero in every part. Diagrams sitting either
side of a tool call therefore both resolved to
`mermaid-artifact-${messageId}-mermaid-0`: one registration overwrote
the other and both cards shared a selection state. The part index the
message context already carries now takes part in the scope.
* fix: keep the Mermaid export menu reachable in fullscreen
The artifact panel gained a fullscreen mode on dev, which re-roots the
panel into the fullscreen element and portals the copy and version
popovers there so they stay visible. The Mermaid export menu portals to
the body, so once these branches met it opened outside the fullscreen
element and rendered invisible. It now takes the same portal target.
* fix: heal Mermaid registrations and cap PNG canvases after rounding
Two findings from the latest review pass.
Closing the panel unmounts Artifacts, whose useArtifacts cleanup wipes
artifactsState while the inline cards stay on screen. The Mermaid card
never observed that, so reopening one card restored only itself and any
other expanded diagram vanished from the version navigator until it was
clicked again. It now subscribes to its own slice and re-registers when
the entry goes missing, matching the self-heal ToolArtifactCard already
documents. The write is a no-op when the entry matches, so it settles.
Rounding each PNG side independently could carry the product back over
the 16.7M pixel budget the scale was picked to satisfy: 3129x50000
resolved to 1025x16374, which is 16,783,350 pixels and enough for a
browser enforcing the area limit to reject toBlob outright. Rounding
down cannot exceed the budget, since the bounding scale is derived from
it.
|
||
|---|---|---|
| .. | ||
| benchmarks | ||
| benchmarks-reasoning | ||
| config | ||
| fixtures/deployment-skills/e2e-deployment-skill | ||
| recordings | ||
| setup | ||
| specs | ||
| config.local.example.ts | ||
| jestSetup.js | ||
| playwright.config.a11y.ts | ||
| playwright.config.benchmark.ts | ||
| playwright.config.local.ts | ||
| playwright.config.mermaid.ts | ||
| playwright.config.mock.ts | ||
| playwright.config.real.ts | ||
| playwright.config.reasoning-perf.ts | ||
| playwright.config.ts | ||
| README.md | ||
| types.ts | ||
LibreChat e2e
The mock e2e profile is the safest default for generated tests. It starts LibreChat with e2e/config/librechat.e2e.yaml, injects an in-process fake LLM (via LIBRECHAT_TEST_RUN_HOOK), creates an authenticated e2e user, and avoids real provider credentials.
Stream Stores and Shards
The mock profile uses the in-memory generation stream store by default. To exercise the same browser scenarios through a real Redis job store and pub/sub transport, start Redis on port 6379 and run:
npm run e2e:mock:redis
Memory mode explicitly disables Redis. Redis mode defaults to database 15 with a LibreChatE2E key prefix, and fails closed: the test server pings Redis and verifies that the generation job manager did not silently fall back to memory. Override REDIS_URI or E2E_REDIS_KEY_PREFIX when needed.
CI runs the complete mock suite in both stream modes. Each mode is split across four Playwright shards, while each shard keeps one worker so tests do not contend for the shard's authenticated user and database:
npx playwright test --config=e2e/playwright.config.mock.ts --shard=1/4
Recording Tests
Use Playwright codegen when you want to turn an exploratory browser session into a draft test:
npm run e2e:record
That command builds the app, starts the LibreChat test server (with an in-process fake LLM) when needed, writes e2e/storageState.json, and opens Playwright codegen at /c/new. The npm script uses http://localhost:3333 so it does not collide with a normal dev server on 3080. Raw recordings are written to e2e/recordings/ and ignored by git.
For a real local LibreChat config instead of the mock profile:
npm run e2e:record:local
Useful direct options:
node e2e/setup/record.js --url=http://localhost:3080/c/new
node e2e/setup/record.js --profile=local --no-output
node e2e/setup/record.js --auth-only
node e2e/setup/record.js --output=e2e/recordings/settings-draft.spec.ts
LLM-Assisted Loop
- Start
npm run e2e:record. - Let the LLM use Computer Use to operate the headed Playwright browser.
- Stop codegen after the workflow is captured.
- Move the useful parts from
e2e/recordings/into a committed spec undere2e/specs/mock/. - Replace brittle generated selectors with role, label, text, or
data-testidlocators. - Add assertions that prove the behavior, not just the clicked path.
- Run the finished spec with
npm run e2e:mock -- <spec name>.
Generated recordings are a draft, not the final test. The committed version should use the shared helpers in e2e/specs/mock/helpers.ts where possible, wait on network or visible UI state instead of fixed sleeps, and keep test data deterministic.