Archify 命名章节导轨(Named Chapter Rail)设计剖析:让 guided views 在 0/N 之前即可被扫描、计数与直达
【免费下载链接】archifyAgent skill for beautiful, verifiable architecture, workflow, sequence,>项目地址: https://gitcode.com/GitHub_Trending/arch/archify
导读
本文围绕 Archify 可视化查看器的「命名章节导轨(Named Chapter Rail)」展开,讲解这一功能如何解决 guided views 在未选中章节时的可发现性缺口:读者在0 / N状态下看不到各章节的编写者命名、也看不到每个章节的规模。全文以项目内研究文档 docs/research-visual-evolution-round-28.md 为主体骨架,结合 archify/assets/template.html 中Archify.guidedViews的真实实现与 archify/test/chapter-rail.test.mjs 的契约测试,讲清该功能的产品契约、6 个外部参照源的借/弃结论、32 条验收标准,以及它在源码中如何以「零 schema 变更」的方式落地。读完后,你将能理解 Archify 如何在保持单一状态所有者(activeIndex)、不新增动效、不改导出边界的前提下,把章节名称、步数、键盘漫游、移动端触摸与辅助技术语义完整地集成进现有 Guided Views 面板。
一、产品问题:0/N 状态下的可发现性缺口
Archify 已经具备完整的 guided views 能力:编译编写者声明的guidedViews、渲染当前章节的 label 与 note、支持 Previous / Next / Play / Show all、提供[与]快捷键、聚焦当前章节的focus数组,并为当前章节逐节点绘制故事轨迹(story trail)。
剩余的缺口出现在章节被选中之前:在0 / N状态下,读者知道「存在 guided views」,却无法一眼扫描这些视图的编写者命名,也无法比较它们的规模,只能逐个点击翻阅。因此本轮研究的产品问题被刻意限定为:
能否在不创建 dashboard、不引入第二套故事状态机、不增加任何新的环境动效的前提下,让全部编写者命名的章节在现有 Guided Views 界面上一目了然、可计数、可直接选中?
研究结论为「可以」。方案是:在现有 Guided Views 面板中加入一个紧凑的、由查看器自身拥有的命名章节导轨(Named Chapter Rail)——在0 / N时立即渲染,展示每个编写者命名的 label,以及由该视图focus数组直接推导出的步数;激活行为则完全委托给现有activate(index)路径。
需要明确边界:这是可发现性与导航改进,不是新的 guided view 模型、不是恢复(restore)系统、不是轮播自动播放模式,也不是导出功能。
二、现有架构证据:导轨所需的原料已经全部存在
2.1 编写者模型已包含导轨所需的全部字段
Archify.guidedViews(archify/assets/template.html)把archify-guided-views-data载荷解析为有序的views数组。每条视图已经具备:
- 稳定的
id,用于#view=深链; - 编写者声明的
label与可选的note; - 有序的
focus数组,既定义高亮节点,也定义当前章节故事轨迹的序列。
导轨必须直接读取这些字段,不得为显示顺序、计数、激活态、状态、图标、时长或视口新增任何 schema 属性。这一点在源码中得到印证:buildChapterIndex()遍历views.forEach(...),直接读取view.id、view.label、view.note、view.focus.length,没有任何新增字段(见 archify/assets/template.html)。
2.2activeIndex是唯一的章节所有者
模块初始化时activeIndex = -1(对应 Show all 状态),只通过activate、activateById、showAll或 hash 恢复来改变;可见计数、label、note、focus、URL、视口揭示(viewport reveal)、播放与分享提示均由该值派生。源码可见:
showAll()把activeIndex = -1并重渲染(archify/assets/template.html);activate(index, options)校验边界后写入activeIndex = index,随后统一走renderStoryTrail、beginHandoff、render(archify/assets/template.html);activateById(id)只是views.findIndex(...)后转调activate(archify/assets/template.html)。
因此导轨必须从activeIndex渲染并调用现有所有者,禁止引入selectedChapter、railIndex、DOM 持有的选中态或第二个 URL 解析器。漫游键盘焦点是「瞬时焦点」,不是章节选中。测试中也显式断言doesNotMatch(/selectedChapter|visitedChapters|completedChapters/),确认源码不存在重复选中状态(archify/test/chapter-rail.test.mjs)。
2.3 章节导轨与节点轨迹是两个层级
现有guided-view-trail只为已选中的视图创建,展示view.focus中的有序节点。新导轨位于其上一级:
- 章节导轨(chapter rail):选择要查看哪个编写者视图;
- 故事轨迹(story trail):当前视图由哪些 focus 节点组成。
章节激活时两者应同时可见。用章节按钮替换节点轨迹会丢弃有价值的关系证据;把每个章节下的节点步骤全部嵌套展开则会变成 dashboard。有界设计就是「一行浅层章节行 + 现有激活章节轨迹」。
三、6 个参照源的借/弃分析
研究文档对 6 个外部参照源做了固定版本的对比分析,核心原则是「取其有界性,弃其越界设计」。
3.1 Fireworks Tech Graph:保持查看器有界、导出干净
参照源固定为yizhiyanhua-ai/fireworks-tech-graph@50c819d。其交互式查看器暴露一个标题、一组聚焦的原生控件、可聚焦的舞台和aria-live缩放状态,而不是一堆次要面板的 dashboard;查看器持有唯一的可变的 pan/zoom 对象与重置路径;导出序列化 SVG 而非周边的视口变换,浏览状态不会变成图表内容;在请求 reduced motion 时移除 CSS 过渡。
借用:单一查看器所有者、原生控件、干净的产物边界、小表面积。不借用:缺失编写者章节发现能力、以及把「实时 SVG 序列化」当作 Archify 导出实现——Archify 的 canonical-clone 清理更强且必须保持不变。
3.2 D2:命名视图需要明确语义与密度护栏
D2 给编写者的板子三种不同含义:layers相互独立、scenarios从基础视图继承、steps从上一个步骤继承;其内部链接与可点击的祖先导航让命名板子直接可达,而不是强迫线性翻阅。D2 的导出指南同时指出:动画 SVG 只适合少量 Steps/Scenarios,板子太多会让查看者困惑或等待整个循环,更大的组合应使用多个 SVG、PDF 页或 PowerPoint 幻灯片。
借用:让编写者命名直接可扫描、保持原有顺序、保留回到根 / Show all 状态的明显路径。不借用:嵌套板子、继承语法、每章节重新布局、动画循环。导轨应保持为「编译后图之上的单行导航」,窄屏上滚动而非换行成多行控件墙。
3.3 Flourish Stories:保存的视图通过命名、进度与直接控件变得可理解
Flourish 把地图位置、菜单选择等交互保存进每个故事幻灯片,而底层可视化数据与设置的更新继续流入所有派生幻灯片;其播放器提供前进/后退导航、编写者字幕、过渡、网页嵌入与响应式桌面/平板/手机输出。默认故事导航包含原生 Previous/Next、当前/总数计数、字幕与进度条,边界按钮在无意义时被禁用。
借用:编写者命名、直接选中、明确的当前/总进度、原生按钮与响应式处理。不借用:通用匿名圆点、新的自动播放循环、附加在普通章节选中上的动画。Archify 已有刻意的 Play/Pause 所有权,不应扩张。
3.4 Mapbox Storytelling:一个章节只应拥有一个连贯状态
参照源固定为mapbox/storytelling@04e6e37。每个章节记录拥有稳定 ID、可见标题、描述、位置与进入/退出效果;渲染器把章节标题暴露为真正的标题而非藏在数字标记后面;章节激活时由单个 handler 解析数组下标、标记激活并应用地图与图层状态;退出 handler 移除激活所有权;移动端规则加宽故事卡片并显式修复触摸滚动。该模板 README 还警告其全页滚动驱动界面在 iframe 中无法按预期工作。
借用:稳定编写者命名与单一下标拥有的激活章节。不借用:滚动位置激活、隐藏触发章节、自动前进、环境旋转、全页布局。Archify 导轨必须通过显式按钮激活,且不得改变现有 embed 边界。
3.5 Cytoscape.js:图、视口与导出是三个独立契约
Cytoscape.js 通过cy.viewport()显式暴露视口状态,通过cy.json()声明式保存/恢复图状态;输入事件模型包含鼠标、触摸与捏合手势;图片导出通过显式full选项区分当前视口与整图。
借用:把图选中、视口揭示与产物导出保持为独立职责。不借用:本切片中的通用图状态快照或当前视口导出选项。当前 Archify 的activate(index)、Show all、canonical 导出与 embed 行为就是产品契约。
3.6 WAI-ARIA 与 WCAG:原生激活、有界焦点、无意外动效
WAI-ARIA 轮播模式要求显式 Previous/Next 控件;若存在自动旋转必须有可见的停止/开始控件,且焦点进入或指针悬停时必须停止、不得未经显式请求重启。对「一次一个」的集合,手动 Tabs 模式使用单个 Tab 停靠点、左右漫游、可选的 Home/End 边界、原生 Enter/Space 激活聚焦项;toolbar 模式对分组控件遵循同样的单停靠点 + 方向键原则。
由于视觉导轨是「图上的导航」而非一组 DOM 标签面板,应使用带标签的 navigation/toolbar 组、真正的<button>元素,并为选中章节使用aria-current="step",而不是在没有真实tabpanel关系时套用tablist语义。
WCAG 要求交互触发的非必要动效必须可禁用,持续超过 5 秒的自动移动内容需要暂停/停止/隐藏机制;prefers-reduced-motion媒体特性传递「移除或替换非必要动效」的请求。
借用:真实按钮、单个漫游 Tab 停靠点、可见选中与显式播放。不借用:仅因键盘焦点移动就自动激活——手动激活让读者能扫描长命名而不改变下方图表。
四、推荐产品契约(Recommended Product Contract)
4.1 结构与布局
保留现有外层控件,在「当前章节文案」与「激活节点轨迹」之间插入一行浅层章节导轨:
← 0 / 4 Explore this system → [01 Overview · 4 steps] [02 Trust boundary · 3] [03 Request path · 5] [04 Recovery · 3] Play story Show all章节激活时,其现有 label、note、故事轨迹、Play/Pause、Previous、Next、[/]与 Show all 行为全部保持不变。导轨只是通往同一个activate(index)函数的额外直接路径。
4.2 可见性与计数
- 只要存在有效
views就渲染导轨,包括activeIndex = -1、可见0 / N状态。 - 保留源数组顺序,不按字母或数量排序。
- 每个
view.label以可见文本展示,不得用圆点、tooltip 或仅章节号替代名称。 - 每个可见步数由
view.focus.length推导,不查询渲染后的 story-trail DOM、不数边、不重复steps字段。 - 仅当当前数据契约允许时,零步视图才保持「有名字但禁用/不可激活」;导轨不得静默发明 focus 目标。
4.3 选中与所有权
- 每个章节项是原生 button,数组下标是其稳定运行时映射。
- 点击、触摸、原生 Enter 与原生 Space 都调用现有
activate(index)路径。 activeIndex仍是唯一选中章节状态;选中样式、aria-current、计数、label、note、hash、focus 与故事轨迹都由它派生。- Show all 继续调用现有
showAll()路径并把导轨带回0 / N,不销毁导轨。 - Previous、Next、
[/]、hash 恢复与刻意的播放通过正常渲染路径更新同一个导轨;没有导轨专属的选中事件去写图状态。 - 不增加恢复栈;现有 Guided Views 仲裁与 Show all 语义保持权威。
4.4 键盘与辅助技术
- 导轨是有可见标签的 navigation 或 toolbar 组。
- 恰好一个可激活章节按钮
tabindex="0",其余tabindex="-1"。 - 左右方向键把焦点移到上一个/下一个可激活章节,边界处有界;它们不选中章节。
- Home/End 把焦点移到第一个/最后一个可激活章节;它们不选中章节。
- Enter/Space 使用原生
<button>激活,不添加第二套合成键到点击处理器(避免双重激活)。 - 选中通过 Previous、Next、直接激活、
[/]、hash 恢复或播放改变时,激活的导轨项获得aria-current="step";除非读者在导轨内操作,否则不抢焦点。 - 可见名称与计数构成可理解的可访问名,例如
Trust boundary, chapter 2 of 4, 3 steps, current。
4.5 视觉语言与密度
- 激活、过去与未来章节的提示在不依赖颜色的情况下仍然可区分:
- 激活:更粗描边 +
CURRENT/当前标记; - 过去:勾选/完成标记 + 更安静的文字;
- 未来:数字标记 + 常规描边。
- 激活:更粗描边 +
focus-visible必须与激活不同——读者可以键盘聚焦一个未来章节而当前章节仍保持选中。- 使用与现有查看器 token 一致的紧凑药丸/卡片,不增加统计、缩略图、按类型图标、边数、完成百分比或第二个侧面板。
- 保持单行:桌面可显示全集或水平溢出;窄屏必须滚动而不是换行成多行。
4.6 移动端与触摸
- 在现有窄断点下,导轨水平可滚动,支持触摸平移与滚动吸附(scroll snapping)。
- 每个章节按钮至少 44 × 44 CSS 像素命中目标。
- 直接触摸激活一次;没有 hover-only 预览或「首次触摸变 hover」的陷阱。
- 实际章节选中后,用非动画/瞬时滚动把激活项居中到导轨;仅因漫游焦点移动不得居中。
- 导轨不得与底部控件重叠、不得制造页面级水平溢出、不得阻挡 SVG pan/zoom 手势。
4.7 动效
- 不动画章节按钮的进入、选中、完成、焦点、导轨滚动或 Show all。
- 只保留现有刻意的 Play/Pause 进度动画与
playing拥有的 story-beat 播放。 - 手动点击/触摸/键盘选中保持静止。
- 现有 reduced-motion 行为保持权威:不新增计时器、过渡或 autoplay 分支。
4.8 产物边界
- 不需要 schema 或渲染器改动;导轨由共享查看器中现有 guided-view JSON 派生。
- 不为导轨新增或改动 canonical SVG 属性。
- 不改
Archify.exporter、导出清理、canonicality 回执、打印输出或图片尺寸。 - 不改 embed 抑制或
?play=1分享播放语义。 - 导轨保持为运行时 HTML,遵守现有
.guided-views/no-print边界;它永远不会进入克隆后的 SVG 输出。
五、源码级实现印证:契约如何真实落地
研究文档的契约并非停留在纸面——Archify.guidedViews模块在 archify/assets/template.html 中已完整实现,archify/test/chapter-rail.test.mjs 用 4 组测试逐条验证。下面把契约与实现一一对应。
5.1 构建:buildChapterIndex()直接消费 views
导轨由document.getElementById('guided-view-index')(nav,aria-label来自 i18n 键viewer.guided.chapters)与guided-view-chapters(<ol>)承载。buildChapterIndex()对每个 view 创建<li><button class="guided-view-chapter">:
button.type = 'button',原生按钮语义;data-guided-view-id = view.id,作为运行时稳定映射;- 序号
<span class="guided-view-chapter-index">用(index + 1 < 10 ? '0' : '') + (index + 1)补零,aria-hidden="true"; - 标题
title.textContent = view.label,即编写者命名直接可见; - 步数
stops.textContent = viewerCount('viewer.guided.chapter.stop', view.focus.length),即从focus.length推导计数; - 初始
tabindex:首个0,其余-1(单 Tab 停靠点)。
(见 archify/assets/template.html。)
5.2 同步:syncChapterIndex()从 activeIndex 派生一切
syncChapterIndex()在每次渲染时被调用(render()内,见 archify/assets/template.html),是「单一状态所有者」原则的直接体现:
var current = index === activeIndex;- 位置推导
activeIndex < 0 ? 'available' : (current ? 'current' : (index < activeIndex ? 'before' : 'after')),写入data-chapter-position,同时同步到li; - 激活项
aria-pressed="true"、aria-current="step",其余移除aria-current; - 激活项展示步数文本并更新可访问名
viewer.guided.chapter.current;非激活项展示由chapterDelta(stay/enter/leave 三段)算出的增量摘要(如=2 +1 −1),并切换为viewer.guided.chapter.delta.*可访问名; - 激活时
centerChapterButton(chapterButtons[activeIndex])用behavior: 'auto'(非动画)居中。
(见 archify/assets/template.html。)注意:契约允许的「过去/未来」视觉差异,在实现中还扩展为对未来章节展示 stay/enter/leave 增量摘要——这是对「可扫描规模」的进一步深化,依然没有新增状态变量。
5.3 选中委托:导轨按钮直接走 activate 路径
chapterList的 click 委托:chapterList.addEventListener('click', ...)里activateById(button.getAttribute('data-guided-view-id')),即通过数组下标映射到现有activate所有者(archify/assets/template.html);- Enter/Space 由原生
<button>激活语义处理,无合成 key-to-click 处理器,与「不双重激活」契约一致; - Previous/Next、
[/]、hash 恢复全部走既有路径:prev/next的 click 直接activate(activeIndex ± 1)(archify/assets/template.html),全局 keydown 里]/[调activate(archify/assets/template.html),syncViewFromHash()通过activateById(initial, { updateUrl: false, restore: true })恢复并让syncChapterIndex()标记对应项为 current(archify/assets/template.html)。
5.4 键盘漫游:瞬时焦点不改变选中
chapterList的 keydown 监听里,ArrowRight / ArrowLeft / Home / End 只计算目标下标并调用focusChapterButton(target),后者更新tabindex分布、button.focus()并centerChapterButton(archify/assets/template.html 与 L9347-L9356)。漫游不触发激活,与 WAI-ARIA toolbar 模式的单停靠点原则一致;activeIndex不变,因此测试断言「Left/Right rove focus without changing activeIndex」。
5.5 动效、移动端、embed 与打印边界
- 选中不带动画:
centerChapterButton使用behavior: 'auto',无过渡; prefers-reduced-motion: reduce时.guided-view-chapter { transition: none !important; },且模块内reducedMotion()守卫让scheduleStoryPlayback()直接返回 false——播放降级为静态路径;- 移动端:
.guided-view-chapter { min-height: 2.75rem; }(≈44px 命中目标)、scroll-snap-type: x proximity、flex: 0 0 min(14rem, 78vw)保证单行滚动不换行; - embed 边界:
html[data-embed="true"] .guided-views { display: none !important; },导轨随面板整体抑制,与?play=1分享播放语义互不干扰; - 打印边界:
.toolbar, .diagram-nav, .focus-chip, .guided-views, .archify-toast, .no-print { display: none !important; },导轨属于.guided-views/no-print运行时 HTML,绝不进入 SVG 导出。
5.6 一处超出原契约的深化:hover/focus 章节预览
实现还在原契约之上增加了showChapterPreview()/setChapterPreviewIntent():指针悬停或键盘聚焦(仅(hover: hover)且非 touch)可暂时在图上标出目标章节的 stay/enter/leave 角色,同时chapterPreviewBlocked()在 playing、handoff、embed、print、route/lens/legend/relationship/intent-trace 激活时全部拦截;focusin进导轨时若正在播放会pausePlayback()(读者接管)。这仍然遵守「手动点击才激活、预览不动效不写状态」的总原则——预览只改 SVG 上的data-chapter-preview-*属性,不触碰activeIndex。
六、借 / 弃矩阵(Explicit Borrow / Skip Matrix)
| 参照源 | 借用 | 跳过 |
|---|---|---|
| Fireworks Tech Graph | 有界查看器控件;单一所有者;干净产物边界 | 把匿名视口状态当故事;live-SVG 导出改动 |
| D2 | 编写者命名;直接可达;明显根路径;密度克制 | 嵌套板子;新继承/schema;动画板子循环 |
| Flourish Stories | 原生 Prev/Next;可见计数/字幕/进度;响应式导航 | 匿名圆点;新 autoplay/loop;选中动画 |
| Mapbox Storytelling | 稳定章节 ID/命名;单一下标拥有的激活态 | 滚动驱动激活;隐藏触发;环境旋转 |
| Cytoscape.js | 图、视口与输出职责分离 | 通用快照/恢复栈;新当前视口导出 |
| WAI/WCAG | 原生激活;漫游焦点;无颜色状态;显式播放 | 焦点跟随选中;意外或持续动效 |
七、32 条验收标准:从契约到可验证
研究文档给出了完整的验收标准清单,与 archify/test/chapter-rail.test.mjs 的断言一一呼应:
- 含有效 guided views 的文档每个视图渲染一个章节导轨项;
- 选中前、计数器显示
0 / N时导轨可见; - 每项可见地暴露编写者
view.label; - 每项暴露等于
view.focus.length的步数; - 导轨顺序与编译后
views数组完全一致; - 点击任一项通过现有
activate(index)所有者恰好激活该下标; - 粗指针上触摸任一项恰好激活一次;
- 聚焦项上原生 Enter 与 Space 恰好激活一次;
- 恰好一个合格项参与页面 Tab 序列;
- 左右方向键漫游焦点且不改变
activeIndex; - Home/End 漫游到首/末合格项且不改变
activeIndex; - 直接导轨激活同步更新现有计数器、label、note、聚焦图、故事轨迹与
#view=hash; activeIndex是唯一选中章节变量,无重复导轨选中状态;- Previous/Next 通过现有渲染路径更新导轨激活态;
- 现有
[/]快捷键更新同一激活态; - 现有 hash 恢复把匹配的导轨项标记为 current;
- Show all 保持导轨可见、清除 current 状态、通过现有
showAll()恢复0 / N; - 激活项有
aria-current="step",非激活项没有; - 激活、过去、未来与键盘聚焦状态在不仅依赖颜色的情况下可区分;
- 手动章节选中无动画或计时器;
- 导轨旁唯一移动的进度仍是 Play/Pause 拥有的播放进度;
prefers-reduced-motion: reduce下导轨无动效、现有播放降级保持完整;- 390 CSS 像素视口下导轨保持单行、可水平滚动、滚动吸附、每项至少 44×44 像素;
- 移动端选中章节居中激活项,无页面水平溢出、不与底部控件重叠;
- 导轨不依赖 hover、不阻挡图触摸手势;
- 无 guided views 的文档渲染空导轨(源码
if (!views.length) return { count: 0, ... }直接短路,archify/assets/template.html); - 不引入渲染器、schema、校验器、fixture 模型或 guided-view JSON 形状改动;
- canonical SVG 内容除无关既有生成改动外字节/结构等价,导轨标记不进入 SVG 源;
- SVG/图片导出与打印不含章节导轨 HTML 或运行时状态;
- 现有 embed 抑制与分享播放行为不变;
- 现有 Prev、Next、Play/Pause、Show all、故事轨迹、节点释放、presentation 与 URL 测试保持绿色;
- 新契约测试覆盖
0 / N、命名/计数、激活委托、漫游焦点、无颜色状态、移动端几何、reduced motion 与产物边界。
测试文件 archify/test/chapter-rail.test.mjs 的第 1 组测试直接对 5 种渲染模式(architecture / workflow / sequence / dataflow / lifecycle,见 archify/test/chapter-rail.test.mjs)渲染 HTML 并断言导轨 nav/ol 结构、buildChapterIndex、views.forEach、补零序号、title.textContent = view.label、viewerCount('viewer.guided.chapter.stop', view.focus.length)存在,且 canonical SVG 中无guided-view-chapter/data-chapter-position;第 2 组断言激活委托与aria-current管理、且无重复状态变量;第 3 组断言键盘优先(focusin 暂停播放、ArrowLeft/Right/Home/End、focusChapterButton、button.type = 'button')且无role="tab"/role="tabpanel"误用;第 4 组断言位置样式、min-height: 2.75rem、scroll-snap-type: x proximity、flex: 0 0 min(14rem, 78vw)、behavior: 'auto'、reduced-motion 过渡移除、embed 抑制与打印抑制。这些测试直接为上文 32 条验收标准中的第 1–30 条提供了可执行证据。
八、决策与总结
最终决策:把Named Chapter Rail实现为Archify.guidedViews的一个浅层扩展。
这里追求的「丰富」不是更多环境动画,而是在承诺之前就让编写者已有的叙事结构变得可读:读者能看见存在哪些故事、每个多长、直接跳到一个、知道自己在哪、并能回到整图。复用views、focus、activeIndex、activate、showAll、刻意的播放与现有导出边界,使得结果既美观、稳定,又一眼可辨是 Archify。从 docs/research-visual-evolution-round-28.md 到 archify/assets/template.html 与 archify/test/chapter-rail.test.mjs,这条「产品问题 → 参照源研究 → 有界契约 → 源码落地 → 契约测试」的完整链路,正是 Archify 每一轮视觉演化研究的标准范式,也值得任何在既有查看器中新增导航能力的产品参考:先证明原料已在手,再限定边界,最后让测试锁定契约。
【免费下载链接】archifyAgent skill for beautiful, verifiable architecture, workflow, sequence,>项目地址: https://gitcode.com/GitHub_Trending/arch/archify
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考