fix(docs): align Mermaid viewer accessible titles
This commit is contained in:
1 parent
ece25ff8e6
commit
91f6733096
6 files changed
+38
-13
No files matched your search
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.md
|
||||
2026-09-14-docs-mermaid-viewer.md: 21d3b0be620d452bfe7024e2eb9e931db4d302b4
|
||||
2026-09-14-docs-mermaid-viewer.zh.md: f7400c5e5d1ba9b58ca05eeaaca422a9583a4a47
|
||||
2026-09-14-docs-mermaid-viewer.md: 03aecb7b4df1c79d2eb254375f6b2c68145079ac
|
||||
2026-09-14-docs-mermaid-viewer.zh.md: b66f427a4820eef0d6fa25b163cfc8e3548ec0c6
|
||||
@@ -10,11 +10,11 @@ Complex Mermaid diagrams lose readable detail when scaled to the documentation c
|
||||
|
||||
## Decision
|
||||
|
||||
The [VitePress theme](../../../../website/.vitepress/theme/index.ts) adds a corner fullscreen icon to each rendered Mermaid SVG. A native modal dialog provides an inert background and Escape dismissal. A floating toolbar groups zoom controls, the current scale, and fit; close stays in the top corner, and help opens on demand. Keyboard focus cycles through all five buttons. Panzoom supplies pointer, wheel, and pinch interaction. Arrow keys pan in fixed screen distances. Closing restores the entry's focus without scrolling and restores the page's previous overflow setting.
|
||||
The [VitePress theme](../../../../website/.vitepress/theme/index.ts) adds a corner fullscreen icon to each rendered Mermaid SVG. A native modal dialog provides an inert background and Escape dismissal. Its visible title also names it for assistive technology; a missing or blank page heading uses the localized viewer title. A floating toolbar groups zoom controls, the current scale, and fit; close stays in the top corner, and help opens on demand. Keyboard focus cycles through all five buttons. Panzoom supplies pointer, wheel, and pinch interaction. Arrow keys pan in fixed screen distances. Closing restores the entry's focus without scrolling and restores the page's previous overflow setting.
|
||||
|
||||
The viewer copies the SVG into a shadow root. Mermaid's embedded selectors and fragment IDs stay local to the copy, so its markers and styles cannot resolve against the original diagram. Panzoom transforms a viewport-sized canvas containing the SVG at its natural viewBox dimensions, so pointer coordinates and the transform origin share the same center; the initial scale and every resize fit the entire diagram without enlarging it beyond its natural size. This preserves vector detail while keeping fit independent of the narrow document column. The canvas has no visible frame; reserved space keeps controls clear of the fitted diagram.
|
||||
|
||||
Viewer resources belong to the mounted theme. Route, language, theme, and source-SVG replacement close the active view; asynchronous Mermaid renders receive a fresh entry. The scroll lock belongs on the body because the Mermaid plugin observes every attribute change on the HTML element and rerenders in response. The [implementation](../../../../website/.vitepress/theme/mermaid-viewer.ts) leaves Markdown, raw page copies, and `llms.txt` generation with their existing owners.
|
||||
Viewer resources belong to the mounted theme. Route, language, theme, and source-SVG replacement close the active view; asynchronous Mermaid renders receive a fresh entry. The body overflow lock relies on the default theme retaining visible overflow on the HTML element. It avoids mutating HTML attributes, which the Mermaid plugin observes and rerenders in response. The [implementation](../../../../website/.vitepress/theme/mermaid-viewer.ts) leaves Markdown, raw page copies, and `llms.txt` generation with their existing owners.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -28,4 +28,6 @@ Viewer resources belong to the mounted theme. Route, language, theme, and source
|
||||
|
||||
The website gains Panzoom as a direct dependency and uses native dialog, shadow-root, and resize-observer support. Viewer zoom and position are transient: resizing refits the diagram, and navigating or changing the theme closes it. Existing page diagrams remain the reading and link-navigation source.
|
||||
|
||||
The [focused tests](../../../../website/tests/mermaid-viewer.spec.ts) cover late rendering, fit dimensions, control wiring, keyboard cycling, replacement, and resource release through `docs:check` and `doc-sync`. Real-browser verification remains necessary for native modal behavior, SVG marker rendering, drag and wheel transforms, themes, and narrow viewports; DOM-only tests do not establish those browser behaviors.
|
||||
The [focused tests](../../../../website/tests/mermaid-viewer.spec.ts) run in the root unit-test suite; `docs:check` and `doc-sync` also select them so documentation-only validation exercises the viewer. They cover late rendering, accessible titles, initial and resized fit, control wiring, keyboard cycling, replacement, and resource release.
|
||||
|
||||
**CI coverage gap.** The DOM tests mock Panzoom and do not execute browser layout. Native modal behavior, SVG markers, pointer-anchored wheel zoom, canvas dragging, theme colors, and narrow-screen geometry require real-browser verification. The recorded browser demonstration supplies evidence for the current implementation, but it is not an automated regression check.
|
||||
@@ -10,11 +10,11 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
[VitePress 主题](../../../../website/.vitepress/theme/index.ts) 在每张已渲染的 Mermaid SVG 角落增加全屏图标。原生模态对话框使背景不可交互,并支持 Escape 退出。浮动工具栏集中显示缩放控件、当前比例和适应窗口操作;关闭按钮位于顶部角落,帮助按需展开。键盘焦点在五个按钮之间循环。Panzoom 提供指针、滚轮和双指交互。方向键按固定的屏幕距离平移。关闭时恢复入口焦点且不滚动页面,并恢复页面原有的 overflow 设置。
|
||||
[VitePress 主题](../../../../website/.vitepress/theme/index.ts) 在每张已渲染的 Mermaid SVG 角落增加全屏图标。原生模态对话框使背景不可交互,并支持 Escape 退出。可见标题也用于辅助技术识别对话框;页面标题缺失或为空白时,使用本地化的查看器标题。浮动工具栏集中显示缩放控件、当前比例和适应窗口操作;关闭按钮位于顶部角落,帮助按需展开。键盘焦点在五个按钮之间循环。Panzoom 提供指针、滚轮和双指交互。方向键按固定的屏幕距离平移。关闭时恢复入口焦点且不滚动页面,并恢复页面原有的 overflow 设置。
|
||||
|
||||
查看器将 SVG 复制到 shadow root 中。Mermaid 内嵌的选择器和片段 ID 限定在副本内部,因此副本的标记和样式不会解析到原始图表上。Panzoom 变换与视口等大的画布,其中的 SVG 使用 viewBox 的自然尺寸,使指针坐标与变换原点共享同一个中心;初始缩放以及每次窗口尺寸变化都会适配整张图表,且不会放大到超出自然尺寸。这样既保留矢量细节,也使适配不受文档窄列宽度的影响。画布没有可见边框;预留空间使控件不会遮挡适配后的图表。
|
||||
|
||||
查看器资源由已挂载的主题持有。路由、语言、主题和源 SVG 替换都会关闭当前视图;异步渲染的 Mermaid 图表会获得新的入口。滚动锁设置在 body 上,因为 Mermaid 插件会观察 HTML 元素的每次属性变化并重新渲染。[实现](../../../../website/.vitepress/theme/mermaid-viewer.ts) 将 Markdown、原始页面副本和 `llms.txt` 的生成保留在原有归属处。
|
||||
查看器资源由已挂载的主题持有。路由、语言、主题和源 SVG 替换都会关闭当前视图;异步渲染的 Mermaid 图表会获得新的入口。body 的 overflow 滚动锁依赖默认主题让 HTML 元素保持 visible overflow。它避免修改 HTML 属性,因为 Mermaid 插件会观察这些属性并重新渲染。[实现](../../../../website/.vitepress/theme/mermaid-viewer.ts) 将 Markdown、原始页面副本和 `llms.txt` 的生成保留在原有归属处。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -28,4 +28,6 @@ Status: implemented
|
||||
|
||||
文档站增加了 Panzoom 直接依赖,并使用原生 dialog、shadow root 和 ResizeObserver 支持。查看器的缩放和平移状态是临时的:窗口尺寸变化会重新适配图表,导航或主题变化会关闭视图。原有页面图表仍然是阅读和链接导航的来源。
|
||||
|
||||
[定向测试](../../../../website/tests/mermaid-viewer.spec.ts) 通过 `docs:check` 和 `doc-sync`(文档同步门禁)覆盖延迟渲染、适配尺寸、控件连接、键盘焦点循环、图表替换和资源释放。原生模态行为、SVG 标记渲染、拖动和滚轮变换、主题以及窄视口仍需要真实浏览器验证;仅运行 DOM 测试不能证明这些浏览器行为。
|
||||
[定向测试](../../../../website/tests/mermaid-viewer.spec.ts) 属于根单元测试集;`docs:check` 和 `doc-sync`(文档同步门禁)也会选中这些测试,使仅运行文档验证时同样覆盖查看器。它们验证延迟渲染、无障碍标题、初始及窗口变化后的适配、控件连接、键盘焦点循环、图表替换和资源释放。
|
||||
|
||||
**CI 覆盖缺口。** DOM 测试模拟了 Panzoom,不执行浏览器布局。原生模态行为、SVG 标记、以指针为锚点的滚轮缩放、画布拖动、主题颜色和窄屏几何布局需要真实浏览器验证。浏览器演示记录提供了当前实现的证据,但不属于自动回归检查。
|
||||
@@ -218,8 +218,7 @@ const wordmark = readFileSync(resolve(import.meta.dirname, '../public/wordmark.s
|
||||
.replace('<svg ', '<svg class="dsh-wordmark" ')
|
||||
|
||||
/**
|
||||
* Styles the default theme does not provide, carried inline because the site
|
||||
* runs the stock theme with no theme directory of its own.
|
||||
* Head-injected styles for the site identity and sidebar scrollbar.
|
||||
*
|
||||
* The navigation-bar lockup pairs with `siteTitle`. The scrollbar rules replace
|
||||
* the sidebar's platform bar, which reserves 15px of a 265px column and draws a
|
||||
|
||||
@@ -22,7 +22,7 @@ const icons = {
|
||||
help: 'M9.1 8a3 3 0 0 1 5.8 1c0 2-3 2-3 4M12 17v.1',
|
||||
}
|
||||
|
||||
/** Theme-owned viewer resources; refresh closes the current view before rescanning. */
|
||||
/** Theme-owned resources; source SVG replacement automatically closes the current view. */
|
||||
export interface MermaidViewer {
|
||||
/** Close the view and update entries after a route, language, or theme change. */
|
||||
refresh(): void
|
||||
@@ -67,12 +67,13 @@ function openDiagram(
|
||||
const doc = svg.ownerDocument
|
||||
const dialog = doc.createElement('dialog')
|
||||
dialog.className = 'dsh-diagram-viewer'
|
||||
dialog.setAttribute('aria-label', copy.title)
|
||||
dialog.setAttribute('aria-labelledby', 'dsh-diagram-title')
|
||||
const toolbar = doc.createElement('div')
|
||||
toolbar.className = 'dsh-diagram-toolbar'
|
||||
const title = doc.createElement('span')
|
||||
title.className = 'dsh-diagram-title'
|
||||
title.textContent = doc.querySelector('.vp-doc h1')?.textContent.trim() ?? copy.title
|
||||
title.id = 'dsh-diagram-title'
|
||||
title.textContent = doc.querySelector('.vp-doc h1')?.textContent.trim() || copy.title
|
||||
const zoomOut = button(doc, copy.zoomOut, 'zoomOut')
|
||||
const zoomIn = button(doc, copy.zoomIn, 'zoomIn')
|
||||
const scaleLabel = doc.createElement('span')
|
||||
|
||||
@@ -84,6 +84,24 @@ function open(): HTMLDialogElement {
|
||||
}
|
||||
|
||||
describe('documentation Mermaid viewer', () => {
|
||||
it.each([
|
||||
{ heading: 'Agent lifecycle', language: 'en-US', expected: 'Agent lifecycle' },
|
||||
{ heading: ' \n ', language: 'zh-CN', expected: '图表查看器' },
|
||||
{ heading: null, language: 'en-US', expected: 'Diagram viewer' },
|
||||
])('uses the visible title as the dialog name for $heading', ({ heading, language, expected }) => {
|
||||
render()
|
||||
if (heading !== null) {
|
||||
const h1 = document.createElement('h1')
|
||||
h1.textContent = heading
|
||||
required(document.querySelector('.vp-doc')).prepend(h1)
|
||||
}
|
||||
viewer = installMermaidViewer(document, () => language)
|
||||
getByRole(document.body, 'button').click()
|
||||
const dialog = getByRole(document.body, 'dialog', { name: expected })
|
||||
const title = required(document.getElementById(required(dialog.getAttribute('aria-labelledby'))))
|
||||
expect(title.textContent).toBe(expected)
|
||||
})
|
||||
|
||||
it('adds one entry only after an SVG with usable dimensions renders', async () => {
|
||||
viewer = installMermaidViewer(document, () => 'en-US')
|
||||
expect(queryAllByRole(document.body, 'button')).toHaveLength(0)
|
||||
@@ -118,6 +136,9 @@ describe('documentation Mermaid viewer', () => {
|
||||
expect(source.outerHTML).toBe(original)
|
||||
expect(document.body.style.overflow).toBe('hidden')
|
||||
expect(document.documentElement.style.overflow).toBe('')
|
||||
required(resize)()
|
||||
expect(panzoom.zoom).toHaveBeenLastCalledWith(0.2, { animate: false })
|
||||
expect(panzoom.pan).toHaveBeenLastCalledWith(0, 0, { animate: false })
|
||||
viewportWidth = 332
|
||||
viewportHeight = 932
|
||||
required(resize)()
|
||||
|
||||
Reference in new issue
Block a user