Regression checklist
This is an internal regression checklist for maintainers. Section and subsection headings are in English; checklist item bodies remain in Chinese (same text as the zh-CN source) so they stay aligned with E2E labels and manual steps used in CN development. Automated coverage is marked ✅自动 in items; everything else is manual and must be rechecked when the related module changes.
Rule source:
.cursor/rules/change-points-as-tests.mdc— every change point must be registered as a verifiable checkpoint.
Field library / ADR-0032 (2026-08-09)
Platform seed apply, scope ACL, designer insert-from-library, and field-library management pages.
- [平台种子 apply]
POST /dataDict/dd-field-gender/apply返回 fields+enums 且带dictRef✅DataDictServiceImplApplyTest - [scope ACL] platform 只读;group 成员可写;user 本人可写 ✅
DataDictAclTest - [E2E 写入] 表设计 JExcel 工具栏「从字段库写入」→ 追加性别 →
gender列落盘 ✅field-library-insert.spec.ts - [E2E 覆盖] 选中行 → 从字段库写入 → 覆盖为
gender✅field-library-insert.spec.ts - [E2E 菜单露出] 项目菜单 → 类型字典 / 字段库页可达;表单新建条目(不手写 JSON)✅
field-library-manage.spec.ts - [手工] 团队项目:字段库 scope=group 条目仅成员可见/可编辑
- [手工] 设置页
/design/table/setting/fieldLibrary新建个人条目 CRUD - [手工] 表字段空态「从字段库写入」→ Modal → 管理字段库深链
Datasource credential encryption at rest / R-DATA-06 · ADR-0024 (2026-08-05)
Encrypt credentials at rest; API and connector paths still receive plaintext; legacy plaintext rows migrate on save.
- [加解密 roundtrip] 明文→密文→明文一致;IV 随机不重复;篡改/错密钥抛异常 ✅
DataSourceCredentialCipherTest - [幂等加密] 已加密值再
encrypt()不二次包裹 ✅DataSourceCredentialCipherTest#encrypt_isIdempotent_doesNotDoubleEncrypt - [存量明文透传] 无
enc:v1:前缀原样返回,不抛异常 ✅DataSourceCredentialCipherTest#decrypt_legacyPlaintext_passesThroughUnchanged - [prod fail-fast] 空密钥 / 仍用仓库默认值 → 启动拒绝 ✅
DataSourceCredentialCipherTest#prodProfile_* - [落库为密文]
POST /ncnb/dataSources后直查 MySQLpassword为enc:v1:...✅ curl +docker exec mysql2026-08-05 - [API 仍收发明文]
GET /ncnb/dataSources/{id}与分页列表password/username为明文,与改动前一致 ✅ curl 2026-08-05 - [渐进迁移] 手工插入明文行 →
GET可读明文 →PATCH重新保存 → MySQL 变为密文 ✅ curl 2026-08-05 - [ACL 路径同覆盖]
DataSourceAcl.requireOwned直查 mapper 路径同样解密,ConnectorCredentialResolver建连仍拿明文 ✅ConnectorCredentialResolverTest(mock 层)+ 手工建连验证需求未变 - [手工 dogfood] 设计器内新建/编辑数据库连接、测试连接、批量删除,UI 无回归(DatabaseConfigForm/index.tsx 未改动,仅后端加解密透明层)——建议下轮 Playwright UX 走查覆盖
Federated login unlink and re-login / ADR-0021 (2026-08-05)
Physical unlink, safe re-link on next IdP login, and no duplicate-account collisions under concurrency.
- [解绑物理删除]
unlink走physicalDeleteById而非deleteById✅FederateUserServiceTest#unlink_isPhysicalDelete_notLogicalDeleteById - [解绑后重登不撞已存在] 无邮箱身份按约定用户名重新挂接 ✅
FederateUserServiceTest#resolveForLogin_relinksOrphanedAccount_byConventionUsername_whenLinkMissingAndNoEmail;有邮箱身份仍走邮箱重新挂接 ✅resolveForLogin_relinksOrphanedAccount_byEmail_whenUsernameNotConventionBased - [防劫持] 候选账号已挂别的 subject 时不重新 挂接 ✅
resolveForLogin_doesNotRelink_whenCandidateAlreadyLinkedToDifferentSubject - [错误码] 极端并发撞库转 409
FederateException,不再裸 500 ✅resolveForLogin_translatesDuplicateKeyExceptionTo409 - [存量数据] Flyway
V15清掉历史软删行 ✅./backend/dev-ensure.sh --restart日志确认迁移成功 +SELECT COUNT(*) FROM user_identity_link WHERE del_flag<>'0'=0 - [Railway 手工] 真实 Google 账号:登录建号 → 账号设置解绑 → 再次 Google 登录 → 秒登成功(原地复用旧账号,无「已存在」错误、无需重新创建项目)——需真实 IdP 回调,无法自动化,redeploy 后人工走一遍
Public API / ADR-0013
PAT/OAuth scopes, rate limiting, REST /api/v1, MCP read/write tools, and account settings UI for token/client management.
- [PAT 哈希] 铸造后库内仅
token_hash/token_hint,无明文 ✅PatTokenCodecTest+PersonalAccessTokenAuthTest - [scope 门禁] 默认只读;写 scope 须显式铸造 ✅
PatScopesTest - [限流骨架] 超配额拒绝 ✅
PublicApiRateLimiterTest - [Redis 集群限流] Redisson
RRateLimiter;超限 DENY;Redis 异常 UNAVAILABLE(fail-closed) ✅PublicApiRateLimiterTest - [手工 dogfood] 登录铸造 →
GET /api/v1/me200;无 token / 坏 token → 401;JWT 调/api/v1/me→ 401 ✅ 2026-08-04 - [projects 列表] PAT +
projects:read→GET /api/v1/projects仅成员项目 ✅PublicApiProjectServiceTest+ curl - [projects 详情] 非成员 403;
projectJSON.profile.dbs空列表 ✅PublicApiProjectServiceTest+ProjectShareSanitizeTest - [手工 dogfood] 铸造 → projects 列表/详情 200;无 PAT / JWT → 401 ✅ 2026-08-04
- [versions 列表] PAT +
versions:read+ 成员 →GET /api/v1/projects/{id}/versions✅PublicApiVersionServiceTest+ curl - [versions 详情] 跨项目 versionId → 404;
projectJSON.profile.dbs空列表 ✅PublicApiVersionServiceTest+ curl - [手工 dogfood] 铸造 → versions 列表/详情 200;缺
versions:read→ 403 ✅ 2026-08-04 - [MCP 只读骨架]
cd mcp && yarn dogfood:tools 五只读 + REST;无写 tool ✅ 2026-08-04(切片 4) - [写 scope 铸造]
projects:write/versions:write可铸造;默认仍只读 ✅PatScopesTest - [POST versions]
versions:write+ 成员 → 创建版本;清profile.dbs;无写 scope → 403 ✅PublicApiVersionServiceTest+ dogfood - [PATCH project]
projects:write+ 成员 → 改projectName;无写 scope → 403 ✅PublicApiProjectServiceTest+ curl - [PUT projectJSON] 写入前清
profile.dbs;非成员 403 ✅PublicApiProjectServiceTest+ curl - [MCP create_version]
yarn dogfood:REST 写 + MCPcreate_version✅ 2026-08-04 - [MCP projects:write]
yarn dogfood:MCPupdate_project+put_project_json;只读 PAT → 403 ✅ 2026-08-04 - [OAuth client 哈希] 注册后库内仅
client_secret_hash/hint,无明文 ✅OAuthClientCodecTest - [OAuth client_credentials] 有效凭证 →
erd_oat_;坏 secret →invalid_client;scope ⊆ 客户端 ✅OAuthClientCodecTest+ curl - [OAuth /api/v1] OAT Bearer →
GET /api/v1/me200;与 PAT 同链;会话 JWT → 401 ✅ curl 2026-08-04 - [OAuth ignore]
/oauth/token/auth/oauth/token在 ignore-urls;无 CORS 放宽 ✅DeadSecurityConfigContractTest - [OAuth PKCE] S256 round-trip;redirect 精确匹配;拒 http 非 localhost ✅
OAuthClientCodecTest - [OAuth authorize] 会话 JWT → 302
code+state;未注册 redirect → JSON 不 302;无 JWT → 401 ✅ curl 2026-08-04 - [OAuth auth_code 换票] public:code+verifier→
erd_oat_;坏 verifier / 重放 code →invalid_grant;code 库仅哈希 ✅ curl 2026-08-04 - [OAuth public 禁 M2M] public client
client_credentials→unauthorized_client✅ curl 2026-08-04 - [OAuth confidential code] 无 secret →
invalid_client;有 secret+PKCE → OAT ✅ curl 2026-08-04 - [OAuth client 管理 UI]
/account/settings?selectKey=oauthClients注册→secret 揭示→复制 ID→吊销 ✅oauth-clients.spec.ts - [PAT 管理 UI]
/account/settings?selectKey=personalAccessTokens铸造→明文揭示→复制→吊销 ✅personal-access-tokens.spec.ts
Diagram readability and shareability / ADR-0016 (continued)
Diagram density, keyboard facades, modal/drawer traps, and share/demo visual parity — mostly E2E-automated under ADR-0016.
- [几何择柄] 竖叠同列表 FK →
data-port=same;截图diagram-port-same-side.png✅relation.spec.ts「PK/FK 与边样式」 - [字段行再压一档]
.erd-field-rowmin-height 22 / line-height 16 / pad 2;FIELD_ROW_H=26✅graphLayout.test.ts+relation.spec.ts「表节点视觉」 - [表节点密表再压] 表头 pad ≤6;字段行 minH 20 / lh 15 / pad 1;
FIELD_ROW_H=24;标题/徽章层次不动;截图diagram-table-node-density.png/demo-table-node-density.png✅graphLayout.test.ts+relation.spec「PK/FK」+demo.spec - [表节点底栏/空表井碎距] 表头/字段行已密不改;添加 margin≤6 + minH≥22;打开表设计 margin≤6×4 + btn minH≥22;空表井 pad≤6 / gap≤4 / marginT≤4;
NODE_FOOTER_H=28;截图diagram-table-fields-empty-dense.png✅relation「PK/FK」+table-field-empty「画布空表」 - [导入后首屏] 空态「导入 DBML」→ 直开关系图 + 节点落入画布可视区;截图
diagram-import-first-screen.png✅dbml-import.spec.ts「空态导入 DBML」
Automated
- 工作台壳外井次密:HomeLayout shell/body ≤12×16;GroupLayout content/body ≤12×16;列表空态 ≤12;截图
workspace-shell-dense.png/group-shell-dense.png✅layout-outlet.spec.ts - 模型树「表/关系」默认展开:不点 switcher 即见三层 +
tree-open-relation✅model-design-ux.spec.ts - 树虚拟滚动:
.ant-tree-list-holder承载(100+ 表不卡)✅model-design-ux.spec.ts - 左树行高密度:treenode ≤24(目标 ~22)/ font ≤13;截图
diagram-left-tree-dense.png✅model-design-ux.spec.ts - 左树工具条/次密距:工具条 ≤32(目标 ~28)/ 新建·搜索控件 ∈24–28;图标不 clip;sider padX ≤20;新建 Tab focus-visible ✅
model-design-ux.spec.ts - CommonTabs / 表设计签头密度:签栏 ≤26(目标 ~24)+ 签头 ≤28;签头 padX≤8 / gap≤4;内签 gutter/marginR≤2;不 clip 标签/关闭;Tab focus-visible + Cmd+1/2/3;截图
diagram-common-tabs-dense.png✅model-design-ux.spec.ts「表设计三签」「表设计内签」 - CommonTabs 签头键盘:←/→ 移焦 + Enter 激活;关闭「关闭
{表名}」;关签焦点归还;内签同构 ✅common-tabs-keyboard.spec.ts - 审批/工单 SQL 明细:
Modal.info首焦「知道了」;Esc/OK 归还「查看SQL」;Tab trap ✅sql-detail-keyboard.spec.ts - 导入跳过校验:二次导入全跳过 → 首焦「知道了」;Esc/OK 归还「解析并导入」;Tab trap ✅
import-skip-warning-keyboard.spec.ts - 工作台 databaseConfig Drawer:新建/编辑首焦「连接名称」;Esc 归还触发器;Tab trap ✅
database-config-drawer-keyboard.spec.ts - JExcel Escape 退格 / 快捷操作:Esc 归还网格;快捷操作首焦「知道了」;Esc 归还;Tab trap ✅
jexcel-grid-keyboard.spec.ts - 版本同步结果弹层:成功/失败首焦「知道了」;Esc 归还「同步」;Tab trap ✅
version-sync-result-keyboard.spec.ts - 版本列表行密度:行 pad-block ≤10 / 标题 ≤14 / 顶栏 ≤32;截图
diagram-version-list-dense.png✅version.spec.ts - 版本工具条二次密度/碎色:控件 ∈24 –28;图标不 clip;增删摘要·hint 色 ≡
--erd-success/--erd-brand/--erd-ink-600;新增钮 Tab focus-visible ✅version.spec.ts - 工单/审批列表密度:标题栏 ≤32(目标 ~24)/ 行 pad-block ≤10 / 动作钮 ∈22–28;图标不 clip;动作钮 focus-visible;截图
approval-list-dense.png/order-list-dense.png✅approval.spec.ts - 设计器次屏 JExcel 密度:工具栏 ≤32 / 表头·行 pad-block ≤10 / 行高 ≤32;图标不 clip;撤销钮 Tab focus-visible;截图
diagram-jexcel-dense.png✅model-design-ux.spec.ts - 元数据应用子签密度:CodeTab/DbTab 栏 ≤26(目标 ~24);不 clip 标签;子签 Tab focus-visible;Cmd+1/2/3 不回归;截图
diagram-code-tabs-dense.png✅model-design-ux.spec.ts - 表设计内签密度:字段/索引/元数据栏 ≤26(目标 ~24);不 clip 标签;内签 Tab focus-visible;Cmd+1/2/3 不回归;截图
diagram-inner-tabs-dense.png✅model-design-ux.spec.ts - 表设计签体内容次密距:侧 pad ≤16 / 底 pad ≤6;unique-hint pad-block ≤10 / 高 ≤32;JExcel 工具栏不 clip;元数据 tip ≤32 + CodeTab padY ≤4;截图
diagram-tab-body-dense.png✅model-design-ux.spec.ts - 设计器空态次密距:字段/索引 Empty margin ≤8(禁 100 / antd XL);空态 pad 贴 tab-body;保留 CTA;截图
diagram-pane-empty-dense.png✅model-design-ux.spec.ts - 欢迎空态次密距:
.erd-welcome-empty__innerpadY≤20 / padX≤16;标题 mt∈[8,12] / 字∈[16,18] / lh≈22;hero ≤180;逆向链 + 左树新增模型;截图diagram-welcome-empty-dense.png✅model-design-ux.spec.ts - AuthBrandShell 次密距:品牌/表单 pad ≤20×16;gap ∈[8,12];门头 mb ∈[8,12];表单 Title mt≤8;项 mb∈[8,16];控件 ∈[24,32];标题 ≥24;登录/
share-invalid/注册同源;键盘 Skip/Tab 不回归 ✅smoke+share+session - LandingChrome /
/compare次密距:次屏 section pad ≤52;对照行 pad ≤12;nav ≤20;footer ≤36;compare hero ≤36;hero 品牌字 ≥36 + 全幅;键盘 Skip/Tab 不回归 ✅landing.spec+compare.spec - 分享成功态 meta / 表清单次密:meta 高 ≤60 / gap ≤2 / stage pad ≤6;表清单 pad ≤6 / 标题 ≤12 / 行 ∈20–26;弹层 body ≤8 / hint mb ≤8;键盘 Esc/Tab trap 不回归 ✅
demo.spec+share-project-keyboard - Home hero CTA 簇次密:actions gap ≤8;secondary 钮 pad ≤4×10;hero gap ≤24 / mb·pb ≤16;主 CTA ≥40;问候 ≥28;Skip→主区→CTA/二级入口/项目卡 + focus-visible 不回归 ✅
home-keyboard - Home 空态/公告次密:空态 pad ≤24×12;二级入口 mb ≤16;项目区 mb ≤20;公告 pt ≤4 / 行 pad ≤4·gap ≤10 / 标题 ≤13;空态 CTA +「更多公告」保留;键盘不回归 ✅
home-keyboard - 设计器次屏碎密度:逆向/DDL Steps ≤10/12;设置 hint mb≤8;SyncConfig
.erd-io-modal;ReverseTable meta 次密 ✅designer-secondary-pane - 导入/导出弹层 Steps 对齐:
.erd-io-modal__stepsmt/mb ≤10/12 · 标题 ≤13;与次屏同阶;键盘 Esc/Tab 不回归;截图diagram-import-steps-dense.png✅reverse-database-keyboard+export-ddl-keyboard - 右键/树操作菜单密度:项高 ∈26–30(目标 ~28)/ padY≤2 / border-box / font ≤13;图标·文案不 clip;
role=menuitem+ ArrowDown/Esc;截图diagram-context-menu-dense.png✅model-design-ux.spec.ts - [字段签空态 CTA] 清字段→字段签见「还没有字段」+「添加第一个字段」→ 网格 + 画布
id;空态消失 ✅table-field-empty.spec.ts - [画布空表字段 CTA] 清字段见
canvas-fields-empty+「添加第一个字段」→ 内联建 NAME → 空态消失 +「添加字段」 ✅table-field-empty.spec.ts - 版本 diff 次屏密度:组头/行 ~24 pad 4×8 + token 色(success/brand/warning);不回归可视化 diff ✅
version.spec.ts - 导入/导出弹 层密度:标题 ≤14 / 头·身·脚 pad ≤8×12(禁头 10×14×8 / 脚 8×14)/ OK ≥28≤32;截图
diagram-import-modal-dense.png/diagram-export-modal-dense.png✅dbml-import/dbml-export - 普通导出页密度:页标题 ≤14 / 卡片 padY ≤20;图标
currentColor→--erd-brand;截图diagram-export-common-dense.png✅export.spec.ts - Home 主导航图标 fill ≡
--erd-brand(erdColors.brand,非组件硬编码)✅layout-outlet.spec.ts「三壳同语言」 - dataTypeDomains 树图标 fill ≡
erdColors.brand(禁裸#DE2910);设置页/design/table/setting/dataType挂载 CRUD ✅dataTypeDomainsSlice.test.ts+datatype-domains-failure - 设置页 chrome 密度:标题 ≤14 / 输入·保存钮 ≤32 / 表单项 mb ≤16;截图
diagram-setting-page-dense.png✅default-field.spec.ts - 数据库配置页密度:标题 ≤14 / 工具条钮 ≤32 / 抽屉输入·保存钮 ≤32;截图
database-config-page-dense.png✅database-config.spec.ts - 账号设置 / Home 项目卡密度:标题 ≤14 / 输入·保存钮 ≤32 / 安全行 padY ≤16;卡 padY ≤28;截图
account-settings-page-dense.png/home-project-cards-dense.png✅account-settings+layout-outlet - 账号 BaseView 左右列次密:
.baseViewgap ≤16(窄屏 ≤12);禁 24;截图同account-settings-page-dense.png✅account-settingsdensify - 顶栏
erd-chrome-actions次密:Home/Group/分享 gap ≤12;Design ≤8;禁 16;截图chrome-actions-dense.png✅layout-outletdensify - 顶栏
erd-chrome-header次密:Home padX ≤16 + brand–nav gap ≤12;Design gap ≤8;禁 padX20 / gap16;截图chrome-header-dense.png✅layout-outletdensify - Home 水平导航 Menu 项次密:padX ∈[8,12];项高 64;命中宽 ≥44;
testid=home-layout-menu;截图home-nav-menu-dense.png✅layout-outletdensify +home-keyboard回归 - Group 侧栏 nav 行距次密:项高 ∈[28,32];padX ∈[8,12];marginY ≤4;
testid=group-layout-sider-menu;截图group-sider-nav-dense.png✅layout-outletdensify +group-keyboard回归 - 设计器侧栏 nav 行距次密:项高 ∈[28,32];padX ∈[8,12];marginY ≤4;
testid=design-layout-sider-menu;截图design-sider-nav-dense.png✅layout-outletdensify + 侧栏键盘 - 版本列表空态井次密:padY≤12 / padX≤8;禁 16×12;保留「保存第一个版本」;截图
version-empty-dense.png✅version.spec「无数据源也可新增版本」 - 项目列表工具条碎距:Space gap ∈[8,12];搜索高 ≤28;工具条高 ≤32;
testid=project-list-toolbar;截图project-*-list-dense.png✅project-surfacedensify +project-list-keyboard回归 - 团队成员工具条碎距:Space gap ∈[8,12];搜索高 ≤28;工具条高 ≤32 / mb≤8;钮 padX∈[8,12];
testid=group-user-toolbar;截图group-user-toolbar-dense.png✅group-layout-navdensify +group-keyboard/add-user-keyboard回归 - Group 用户组 Title/左角色签碎距:标题 ≤14/lh≤24/mb≤8;左签 padX∈[8,12]·高∈[28,32];
testid=group-setting-page;截图group-setting-page-dense.png✅group-layout-navdensify +group-keyboard/add-user-keyboard回归 - Group 基本设置页头碎距:标题 ≤14/lh≤24/mb≤8/mt≤4;
testid=basic-setting-page;截图group-basic-setting-dense.png✅group-basic-settingdensify +group-layout-nav/group-keyboard回归 - Group 基本设置 Form 碎距:项 mb≤16(目标12);Input/Select/钮高≤32(目标28);label≤13;禁默认 24/32 ✅
group-basic-settingdensify +group-layout-nav/group-keyboard回归 - Group 基本设置删区碎片:Divider≤16;body gap≤12;次文≤13/lh≤20;标题 mb≤2;
testid=basic-setting-delete-zone;确认/aria 不弱化 ✅group-basic-settingdensify +group-project-delete-keyboard - 个人/最近/团队/公告列表行密度:行 pad-block ≤10 / 标题 ≤14 / 打开钮 ≤32;截图
project-person-list-dense.png/project-recent-list-dense.png/project-group-list-dense.png/project-notice-list-dense.png✅project-surface.spec.ts+project-notice.spec.ts - 用户手动折叠模块不被默认展开回顶 ✅
model-design-ux.spec.ts - 表设计三签:签头表名/模型层级 + 字段/索引/元数据应用切换 ✅
model-design-ux.spec.ts - 画布「索引」→ 表设计索引签(无死 affordance;再入仍落索引)✅
relation.spec.ts「画布打开索引签」 - [索引签空态 CTA] 画布→索引见「还没有索引」+「添加第一个索引」→ toast「索引更新成功」+ 表头「索引名*」+
T_TABLE_1_IDX1;空态消失 ✅relation.spec.ts「索引签空态 CTA」 - [索引签再加一行 CTA] 首条后见「+ 再添加一条索引」→ toast +
T_TABLE_1_IDX2;index-add-row仍可见 ✅relation.spec.ts「索引签再加一行 CTA」 - [索引 签删除二次确认] 「删除索引
{name}」→ 取消保留;确认 toast「索引更新成功」+ 空态「添加第一个索引」;index-delete-list消失 ✅relation.spec.ts「索引签删除二次确认」 - [JExcel 工具栏删除二次确认] 字段签选中行→「删除选中行」→ 取消保留;确认后网格与画布无该字段 ✅
relation.spec.ts「JExcel 工具栏删除二次确认」+ 键盘 ✅jexcel-toolbar-delete-keyboard.spec.ts - [表设计字段签 Tab 焦点序] hint→撤销→重做→末尾增加一行;Enter 增行;网格 Shift+Tab 回「快捷操作」 ✅
relation.spec.ts「工具栏 Tab 可达」 - [设计器 Skip + 焦点环] 首项 Tab「跳到模型树」→
erd-design-tree→ Tab 入搜索;「跳到主工作区」→erd-design-workspace→ Tab 离地标(无 trap) ✅relation.spec.ts「设计器 Skip」 - [画布字段浏览器 Tab 环] 选中表 Tab 穿字段→添加字段→开表设计后可脱出;未选中表
tabIndex=-1✅relation.spec.ts「字段浏览器 Tab 环」 - [画布 chrome Tab 序] Controls 四钮 → 工具栏;MiniMap svg
tabindex=-1;Controls:focus-visiblebrand 环;Shift+Tab 回 Controls 无 trap ✅relation.spec.ts「画布 chrome Tab 序」 - [左树键盘漫游] Skip→↓入树;active
data-tree-kb-activebrand 环;Enter 定位表 + 开关系;Skip→Tab 进搜索无 trap ✅relation.spec.ts「左树键盘漫游」 - [画布节点级 Tab] 无选中无 RF node wrapper/
erd-edge-label进序;选中边 chip Enter 开基数;Frame Enter 重命名 ✅relation.spec.ts「画布节点级 Tab」 - [分享壳键盘]
/demo首项 Tab Skip「跳到关系图」→ stage → Tab 离地标;Controls 三钮进序;MiniMaptabindex=-1;适应画布 focus-visible brand 环 ✅share.spec.ts「分享壳键盘」 - [登录壳键盘]
/login首项 Tab Skip「跳到登录表单」→#auth-form-anchor→ Tab 入用户名;密码 Enter 错误凭证 toast;登录钮 focus-visible brand 环;无 trap ✅session.spec.ts「登录壳键盘」 - [注册壳键盘]
/registerSkip「跳到注册表单」→#auth-form-anchor→ 字段 Tab 序(tip 出序);末字段 Enter 密码不一致 toast;注册钮 focus-visible brand 环;无 trap ✅session.spec.ts「注册壳键盘」 - [落地页键盘]
/首项 Tab Skip「跳到主操作」→#landing-main-cta→ Tab 入「在线试用」;试用→注册→登录可逆;主 CTA focus-visible surface 环;无 trap ✅landing.spec.ts「落地页键盘」 - [竞品对照页键盘]
/compare首项 Tab Skip「跳到主操作」→#landing-main-cta→「打开演示」→「自部署指南」→「返回产品首页」可逆;surface focus-visible;无 trap ✅compare.spec.ts「竞品对照页键盘」 - [Home 工作台键盘]
/home首项 Tab Skip「跳到主内容」→#home-main-content→继续建模→新建→示例→二级入口→项目卡可逆;brand focus-visible;无 trap ✅home-keyboard.spec.ts「Home 键盘」 - [GroupLayout 壳键盘]
/project/group/setting/basic首项 Tab Skip「跳到主内容」→#group-main-content→项目名→标签→项目描述可逆;侧栏链 brand focus-visible;无 trap ✅group-keyboard.spec.ts「Group 键盘」 - [项目列表行键盘] 个人/最近/团队 stretched link 消死卡;Enter 开设计器;Tab 行内动作可逆;行
:hasbrand focus-visible;无 trap ✅project-list-keyboard.spec.ts - [账号设置壳键盘]
/account/settings首项 Tab Skip「跳到主表单」→#account-settings-form→邮箱→电话→更新基本信息可逆;brand focus-visible;无 trap ✅account-settings-keyboard.spec.ts - [项目动作弹窗键盘] 新建首焦类型 / 修改首焦项目名 / 删除首焦「是」;Esc 关窗归还触发器;Tab trap 在 dialog ✅
project-action-modals-keyboard.spec.ts - [导入导出弹层键盘] 空态导入首焦 DBML文本 / 菜单导出首焦模型 Select;Esc 归还 CTA/项目菜单;Tab trap ✅
import-export-keyboard.spec.ts - [版本动作弹窗键盘] 新增/编辑首焦版本号(非最新编辑首焦描述)/ 删除·回滚首焦「是」;Esc 关窗归还触发器;Tab trap 在 dialog ✅
version-action-modals-keyboard.spec.ts - [版本对比/详情 diff 键盘] 比对首焦「初始版本」/ 详情首焦「导出变更清单」;Esc 关窗归还触发器;Tab trap 在 dialog ✅
version-diff-keyboard.spec.ts - [同步配置/重建版本键盘] 同步配置首焦「字段增量」/ 重建版本首焦「版本号」;Esc 关窗归还触发器;Tab trap 在 dialog ✅
version-sync-rebuild-keyboard.spec.ts - [重建基线确认键盘] 重建版本表单提交→「重建基线」确认首焦「重建」;Esc 关确认不落盘、归还重建钮;Tab trap ✅
version-rebuild-confirm-keyboard.spec.ts - [初始化基线键盘] 有数据源且未建基线时首焦「版本号」;Esc 关窗归还触发器;Tab trap 在 dialog ✅
version-init-keyboard.spec.ts - [复刻弹层键盘] 版本行「复刻」首焦「项目名」;Esc 关窗归还触发器;Tab trap 在 dialog ✅
project-copy-keyboard.spec.ts - [数据源设置键盘] 项目菜单→数据源设置首焦「新增数据源」;Esc 关窗归还「项目菜单」;Tab trap 在 dialog ✅
database-setup-keyboard.spec.ts - [默认项设置键盘] 项目菜单→默认项设置首焦「默认字段」;Esc 关窗归还「项目菜单」;Tab trap 在 dialog ✅
default-setup-keyboard.spec.ts - [数据源逆向解析键盘] 项目菜单→数据源逆向解析首焦「数据源」;Esc 关窗归还「项目菜单」;Tab trap 在 dialog ✅
reverse-database-keyboard.spec.ts - [导出DDL键盘] 项目菜单→导出DDL首焦「数据源」;Esc 关窗归还「项目菜单」;Tab trap 在 dialog ✅
export-ddl-keyboard.spec.ts - [解析ERD文件键盘] 项目菜单→解析ERD文件首焦上传区「选择ERD文件」;Esc 关窗归还「项目菜单」;Tab trap 在 dialog ✅
reverse-erd-keyboard.spec.ts - [解析PdMan文件键盘] 项目菜单→解析PdMan文件首焦上传区「选择PdMan文件」;Esc 关窗归还「项目菜单」;Tab trap 在 dialog ✅
reverse-pdman-keyboard.spec.ts - [修改密码键盘]
/account/settings?selectKey=security→「修改密码」首焦「密码」;Esc 关窗归还触发器;Tab trap 在 dialog ✅reset-password-keyboard.spec.ts - [修改密码失败不关窗] mock
settings/update业务码 → toast「模拟更新密码拒绝」+ 窗仍开 → 二次成功关窗+「更新密码信息成功」 ✅reset-password-failure.spec.ts - [同步 配置失败不关窗] mock
project/save(upgradeType=rebuild)业务码 → toast「模拟同步配置拒绝」+ 窗仍开 → 二次成功关窗+「设置成功」 ✅sync-config-failure.spec.ts - [默认项设置失败不关窗] mock
project/save(sqlConfig 标记)业务码 → toast「模拟默认项设置拒绝」+ 窗仍开 → 二次成功关窗+「设置成功」 ✅default-setup-failure.spec.ts - [数据源设置确定失败不关窗] mock PUT
dataSources(url 标记)业务码 → toast「模拟数据源保存拒绝」+ 窗仍开 → 二次成功关窗+「保存成功!」 ✅database-setup-failure.spec.ts - [EntityModal 落盘失败不关窗] mock
project/save(modules 含新模型名)业务码 → toast「模拟模型保存拒绝」+ 窗仍开 → 二次成功关窗+「模型添加成功」;键盘闭环不回归 ✅entity-modal-failure.spec.ts+entity-modal-keyboard.spec.ts - [画布关系图弹层落盘失败不关窗] mock
project/save(diagrams 含新图名)业务码 → toast「模拟关系图保存拒绝」+ 窗仍开 → 二次成功关窗+「已新建关系图」+ switcher ✅diagram-modal-failure.spec.ts - [画布关系图弹层键盘] 工具栏「新建关系图」→ 首焦「关系图名称」;Tab trap;Esc 关窗归还触发器 ✅
diagram-modal-keyboard.spec.ts - [数据类型字典弹层键盘] 设置页「新增字段类型」→ 首焦「类型名称」;Tab trap;Esc 关窗归还触发器 ✅
datatype-domains-keyboard.spec.ts - [数据类型字典枚举域 UX] 「新增枚举」→ kind=enum + values[] 落盘;列表种类/取值;编辑追加取值;定位 role/aria/testid ✅
datatype-enum-ux.spec.ts - [字段类型下拉区分枚举] 字典建枚举 → 画布 type select 有 optgroup「枚举」→ 选 code 落盘 + 浏览态徽章;定位 combobox/testid;勿扫
.ant-*✅field-type-enum-picker.spec.ts - [逻辑类型 apply 方言映射]
/setting/dataType新增/编辑逻辑类型 → 填 MYSQL/PG/… 物理类型 → save 含apply[code].type;枚举弹层无 apply 编辑器;定位 role/aria/testid ✅datatype-apply-ux.spec.ts - [画布表头改名落盘失败不退出编辑] mock
project/save(entities 含新表名)业务码 → toast「模拟表改名保存拒绝」+ 编辑态仍开(草稿保留、旧节点 id)→ 二次成功改节点 id ✅table-rename-failure.spec.ts - [画布建表/行内加字段落盘失败可重试] mock save(entities 含 T_TABLE_1 / fields 含新字段)→ toast + 无节点或仍编辑草稿 → 重试成功上图/落字段;空字段 CTA / 空名 toast 不回归 ✅
canvas-create-field-failure.spec.ts - [画布字段改名/删字段落盘失败可重试] mock save(改名 fields 含新名 / 删缺 NAME)→ toast + 仍编辑或行仍在+确认可再删 → 重试成功;空名 toast / 删二次确认不回归 ✅
canvas-field-rename-delete-failure.spec.ts - [画布字段 meta 落盘失败可重试] mock save(PK / Integer 类型 / relationNoShow / 浏览态 PK)→ toast + 勾选/类型回滚或行仍可见 → 重试成功;happy 路径 relation 不回归 ✅
canvas-field-meta-failure.spec.ts - [表设计 JExcel 字段 meta 落盘失败可重试] mock save(字段签 PK / 隐藏)→ toast + 勾选回滚 → 重试成功 + 画布对齐;工具栏删行/半成品/空态 CTA 不回归 ✅
jexcel-field-meta-failure.spec.ts - [表设计索引签落盘失败可重试] mock save(添加第一个索引 / 勾是否唯一)→ toast + 空态或勾选回滚 → 重试成功 + 画布 UK;空态 CTA/再加/删除/键盘不回归 ✅
jexcel-index-failure.spec.ts - [默认字段落盘失败可重试] mock save(改默认主键英文名)→ toast + 网格回滚仍为 id → 重试成功 + 新表带重命名字段;既有成功路径不回归 ✅
default-field-failure.spec.ts/default-field.spec.ts - [画布删表落盘失败可重试] mock save(全 modules 缺 T_TABLE_1)→ toast「模拟删表保存拒绝」+ 节点仍在+确认可再删(无「表删除成功」)→ 重试移出;二次确认/键盘不回归 ✅
canvas-delete-table-failure.spec.ts/relation「画布删表/删边二次确认」 - [左树删模型/关系图落盘失败可重试] mock save(缺关系图名 / 缺模块名)→ toast 模拟拒绝 + 树/表仍在+确认可再删(无成功 toast)→ 重试移出;二次确认不回归 ✅
tree-delete-module-diagram-failure.spec.ts/multi-diagram「左树删除关系图/模型二次确认」 - [画布删边/删分组落盘失败可重试] mock save(缺 T_ORDER→T_TABLE_1 关联 / groups 空)→ toast 模拟拒绝 + 边/框仍在+确认可再删(无成功 toast)→ 重试移出;二次确认/键盘不回归 ✅
canvas-delete-edge-frame-failure.spec.ts/canvas-delete-edge-frame-keyboard.spec.ts/diagram-frame「删除分组二次确认」 - [左树剪切/粘贴表落盘失败可重试] mock save(含 T_TABLE_1副本 / 缺 T_TABLE_1)→ toast 模拟拒绝 + 无副本或表仍在(无成功 toast / 无「剪切成功」)→ 重试粘贴出现副本 / 剪切移出;定位 role/aria(勿扫
.ant-*) ✅tree-cut-paste-failure.spec.ts - [画布拖表坐标落盘失败可回滚] mock save(layout 位移 T_TABLE_1)→ toast「模拟布局保存拒绝」+ transform 回滚(无已保存伪装)→ 重试拖动成功;定位
rfNode/save-status(勿扫.ant-*) ✅canvas-drag-reposition-failure.spec.ts - [画布对齐/自动布局落盘失败可回滚] mock save(左齐两表同 x / 自动布局位移)→ toast 拒绝 + transform 回滚 → 重试成功;定位
align-left/aria-label=自动布局/rfNode(勿扫.ant-*) ✅canvas-align-layout-failure.spec.ts - [Frame 改名/适应成员落盘失败可回滚] mock save → toast「模拟分组保存拒绝」+ 改名草稿保留/适应成员 RF+store 回滚(无「已适应成员」)→ 重试成功;定位
frame-rename-*/diagram-frame/ 「适应成员」(勿扫.ant-*) ✅canvas-frame-rename-bounds-failure.spec.ts - [Frame 新建/成员加减落盘失败可回滚] mock save → toast「模拟分组保存拒绝」+ 新建不上图/加入成员仍 0(无成功 toast)→ 重试成功;定位「新建分组」/「加入分组」/
diagram-frame(勿扫.ant-*) ✅canvas-frame-members-failure.spec.ts - [画布连线建关联落盘失败可重试] mock save → toast「模拟连线保存拒绝」+ 不上边(无
erd-edge-label)→ 重试成功上边;定位rfNode/data-handleid/erd-edge-label(勿扫.ant-*) ✅canvas-connect-edge-failure.spec.ts - [画布改边基数落盘失败可重试] mock save → toast「模拟基数保存拒绝」+ chip 仍
n:1→ 重试成功1:1;定位erd-edge-label/erd-edge-cardinality/role=option(勿扫.ant-*) ✅canvas-cardinality-failure.spec.ts - [画布边 FK 元数据可编辑] 点 chip → 约束名 + ON DELETE/UPDATE;失败 toast 保持空 → 重试 CASCADE/RESTRICT +
fk_order_user落盘 + 刷新;定位勿扫.ant-*✅canvas-fk-meta-edit.spec.ts - [数据类型字典落盘失败可重试] 设置页新增类型 mock save → toast「模拟数据类型保存拒绝」+ 窗仍开、表无新行 → 重试成功入表;定位
datatype-domains-page/role=dialog/ aria-label(勿扫.ant-*) ✅datatype-domains-failure.spec.ts - [ERD 导入落盘失败可重试] 项目菜单→解析ERD mock save → toast「模拟ERD导入保存拒绝」+ 窗仍开、树无「ERD导入」→ 重传成功入树;定位
role=dialog/ complementary(勿扫.ant-*) ✅import-erd-failure.spec.ts - [默认数据源切换落盘失败可重试] 数据源设置:两源切默认 mock save → toast「模拟默认数据源保存拒绝」+「当前使用的数据源」仍为第一源 → 重试切到第二源;定位
role=radioaria「设为默认数据源 …」(勿扫.ant-*) ✅default-db-failure.spec.ts - [版本回滚落盘失败可重试] 存 1.0.0→加 REMARK→存 1.0.1→回滚 1.0.0 mock save → toast「模拟回滚保存拒绝」+ 窗仍开 + 画布仍有 REMARK → 重试成功字段消失;定位
role=dialog「回滚版本」/aria-label=回滚版本(勿扫.ant-*) ✅version-revert-failure.spec.ts - [WORD 模板下载空/JSON 假文件] 默认项设置→默认配置→「下载模板」mock 空 blob / JSON 错误体 → toast「下载模板出错」+ 无 download 事件;定位
role=dialog「默认项设置」/role=button「下载模板」(勿扫.ant-*) ✅word-template-download-failure.spec.ts - [Word gendocx 空/JSON/非 ZIP 假文件] 导出文件页→「导出Word」mock JSON / 空 blob / 非 ZIP octet-stream → toast「Word导出失败」+ 无 download;定位
role=button「导出Word」/testid=export-common-page(勿扫.ant-*) ✅word-gendocx-download-failure.spec.ts - [SQL审批键盘] 团队项目→版本「提交工单」→详情「SQL审批」首焦「审批人」;Esc 关窗归还触发器且父详情仍开;Tab trap ✅
sql-approval-keyboard.spec.ts - [添加成员键盘] 团队项目→权限组「团队普通成员」→「添加成员」首焦「选择用户」;Esc 关窗归还触发器;Tab trap ✅
add-user-keyboard.spec.ts - [只读分享键盘] 设计器顶栏「只读分享」首焦「分享链接」;Esc 关窗归还触发器;Tab trap ✅
share-project-keyboard.spec.ts - [EntityModal 键盘] 设计器空态「新增模型」首焦「名称」;Esc 关窗归还触发器;Tab trap ✅
entity-modal-keyboard.spec.ts - [画布删表确认键盘] RF 选中表 Delete → 首焦「删除」;Esc 关窗归还不删;Tab trap ✅
canvas-delete-table-keyboard.spec.ts - [画布删边/删分组确认键盘] RF 选中边/分组 Delete → 首焦 「删除」;Esc 关窗归还不删;Tab trap ✅
canvas-delete-edge-frame-keyboard.spec.ts - [画布删字段确认键盘] 字段浏览器 ×「删除字段」→ 首焦「删除」;Esc 关窗归还不删;Tab trap ✅
canvas-delete-field-keyboard.spec.ts - [表设计删索引确认键盘] 索引签「删除索引」→ 首焦「删除」;Esc 关窗归还不删;Tab trap ✅
table-index-delete-keyboard.spec.ts - [JExcel 工具栏删行确认键盘] 字段签「删除选中行」→ 首焦「删除」;Esc 关窗归还不删;Tab trap ✅
jexcel-toolbar-delete-keyboard.spec.ts - [JExcel Escape 退格 / 快捷操作键盘] 字段签双击单元格 → 打字 → Esc → 焦点
jexcel-grid、草稿不落盘、签仍开;「快捷操作」→ 首焦「知道了」;Esc 归还;Tab trap ✅jexcel-grid-keyboard.spec.ts - [版本同步结果键盘] 版本行「同步」(无悬停)→确认→模拟成功/失败 → 首焦「知道了」;Esc 归还
version-sync-btn;Tab trap;禁modules空炸 ✅version-sync-result-keyboard.spec.ts - [左树删表确认键盘] 表操作→删除表 → 首焦「删除」;Esc 关窗归还不删;Tab trap ✅
tree-delete-keyboard.spec.ts - [数据源设置删确认键盘] 新增数据源→删 → 首焦「删除」;Esc 关确认归还删钮不删;外层配置窗仍开;Tab trap ✅
database-setup-delete-keyboard.spec.ts - [工作台 databaseConfig 删/批删确认键盘] 行删 + 批删 → 首焦「删除」;Esc 关确认归还触发器不删;Tab trap ✅
database-config-delete-keyboard.spec.ts - [工作台 databaseConfig Drawer 键盘] 新建/编辑 → 首焦「连接名称」;Esc 归还触发器;Tab trap ✅
database-config-drawer-keyboard.spec.ts - [只读分享吊销确认键盘] 分享→吊销确认 → 首焦「吊销」;Esc 关确认归还吊销钮不吊销;外层分享窗仍开;Tab trap ✅
share-revoke-keyboard.spec.ts - [团队项目删确认键盘] 基本设置→删确认 → 首焦「删除」;Esc 关确认归还删钮不删;Tab trap ✅
group-project-delete-keyboard.spec.ts - [团队成员移除确认键盘] 权限组普通成员→移除确认 → 首焦「移除」;Esc 关确认归还移除钮不移;Tab trap ✅
group-user-remove-keyboard.spec.ts - [审批动作确认键盘] API 种子→审批拒绝/通过 + 工单撤销确认 → 首焦主操作;Esc 关确认不落盘;Tab trap ✅
approval-action-keyboard.spec.ts - [审批/工单 SQL 明细键盘] API 种子→审批/工单「查看SQL」→ 首焦「知道了」;Esc/OK 归还触发器;Tab trap ✅
sql-detail-keyboard.spec.ts - [404/403 壳键盘] 未知路径 /
/403首项 Tab Skip「跳到主操作」→#exception-main-cta→「打开示例 demo」→「返回首页」可逆;主 CTA focus-visible brand 环;无 trap ✅not-found.spec.ts「404/403 壳键盘」 | - [分享失效门键盘]
/s/not-a-real-…首项 Tab Skip「跳到主操作」→#exception-main-cta(share-invalid-gate)→「打开示例 demo」→「返回首页」可逆;主 CTA focus-visible brand 环;无 trap ✅share.spec.ts「分享失效门键盘」 - [表设计字段签半成品不静默丢] Tab/Delete/Enter 清空类型 → toast「有行未填完必填项」;Esc 仍在字段签;画布 NAME 仍在 ✅
relation.spec.ts「半成品行不静默丢」 - [表设计索引签半成品不静默丢] 添索引 → Tab/Delete/Enter 清字段 → toast;Esc 仍在索引签;删入口仍在;画布重入索引名仍在 ✅
relation.spec.ts「索引签:半成品行不静默丢」 - 画布「字段」→ 表设计字段签(无死 affordance;再入仍落字段)✅
relation.spec.ts「画布打开字段签」 - 画布「元数据」→ 表设计元数据应用签(无死 affordance;再入仍落元数据应用)✅
relation.spec.ts「画布打开元数据应用签」 - [元数据应用修改/删除字段签对齐] 版本基线后改类型→「修改字段」含 MODIFY 不含 DROP;「删除字段」空(无 MODIFY/DROP) ✅
relation.spec.ts「元数据应用:修改/删除字段签标签对齐模板」
Manual
- [大模型树滚动] 灌 100+ 表(
__ERD_E2E__.ensureTables)→ 左树滚动流畅、搜索命中可见 - [工作区留白] 模型设计/表设计页四边有 12px 留白,画布圆角面板不贴边
Multiple relation diagrams / ADR-0017 Phase 2a (2026-08-02)
Multi-diagram switcher, tree diagram list, command palette locate, and lazy migration from legacy graphCanvas.
Automated
- 工具栏新建/重命名/切换关系图 + 树图列表 + 布局按图持久化/刷新 ✅
multi-diagram.spec.ts - 左树重命名关系图:菜单接通
renameDiagram;无空 FK 弹层;无复制/ 剪切死项 ✅multi-diagram.spec.ts「左树重命名关系图」 - 左树新建关系图:树头「新建」→ menuitem →
createDiagram;树 + switcher + toast ✅multi-diagram.spec.ts「左树新建关系图」 - 左树「关系」文件夹 +:
getByRole('button', { name: '新建关系图' })(scope tree)→createDiagram;树 + switcher + toast ✅multi-diagram.spec.ts「左树「关系」文件夹 + 直建图」 - 左树「编辑表」→ 表设计字段签;「重命名表」另项弹层 ✅
multi-diagram.spec.ts「左树「编辑表」开表设计字段签」 - 左树搜索 × 清过滤 + 无匹配空态:「未找到匹配的表」;× 后树复现表名 ✅
multi-diagram.spec.ts「左树搜索:无匹配空态」 - 命令面板搜表定位:搜表名 → 选中 + fitView +
data-locate-flash;视口外可拉回 ✅relation.spec「命令面板:搜表定位」 - 左树点表定位:点树表名 → 选中 + fitView +
data-locate-flash;不开表设计 ✅relation.spec「左树点表」 - 快捷键速查:
?/ 工具栏 → aria dialog「快捷键」;含 Cmd+K、Delete 确认、Tab 字段导航、Cmd+1/2/3 表设计签;密度 list≤2×4 / row padY≤6;Esc + 关闭钮可焦;与 Cmd+K 互斥 ✅relation.spec「快捷键速查」 - 表设计 Cmd/Ctrl+1/2/3:字段 / 索引 / 元数据应用;输入框内不拦 ✅
relation.spec「表设计 Cmd/Ctrl+1/2/3」 -
getActiveDiagram懒迁移 / tab entity 往返 ✅diagram.test.ts - schema 含
diagrams✅validate-projectjson.mjs - 公开 demo / 示例:双图「鉴权核心」「会话与审计」+ 切换器 ✅
demo.spec.ts/activation.spec.ts
Manual
- [旧项目打开] 仅有
graphCanvas、无diagrams的项目 → 打开画布见主关系图,拖动后 projectJSON 出现diagrams[0] - [分享页] 含
diagrams的项目分享链接 → 只读画布用主图布局 ✅demo.spec.ts(/demo) - [分享只读切图]
/demo见diagram-switcher「鉴权核心」→ 选「纵向视图」→sys_userlayout x 变 + Frame「核心实体」;截图demo-share-diagram-switch.png✅demo.spec.ts - [demo 布局无重叠]
/s/public-demo8 表卡片两两不重叠、边不穿卡(dagre 分层替代手排 x/y,用户反馈7bbcbfa手排失败)✅graphLayout.test.ts「节点两两不重叠」+ Playwright 截图人工核对 - [分享画布视口铺满]
/demoshare-relation-canvas高 >480 且占视口过半、贴近视口底;截图demo-share-canvas-viewport.png✅demo.spec.ts
In-diagram Frame groups / ADR-0017 Phase 2b (2026-08-02)
Visual Frame nodes, member drag/resize/fit, import frame suggestions, and demo/share diagram polish.
Automated
- 选中表→新建分组→
memberEntityIds写入 + 刷新仍见框 ✅diagram-frame.spec.ts - 空分组→选表→加入分组 ✅
diagram-frame.spec.ts - 选中分组→NodeResizer 拉大→
w/h持久化 ✅diagram-frame.spec.ts - 拖框→成员表同向平移 ✅
diagram-frame.spec.ts - 选中分组→「适应成员」重算包围盒 ✅
diagram-frame.spec.ts - 双击 Frame 标题重命名 →
groups[].name持久化 ✅diagram-frame.spec.ts「重命名」 - 边基数 chip 可改
1:1|1:n|n:1|n:n+ 刷新仍在 ✅relation.spec.ts「表节点视觉」 - Crow's foot:默认
n:1→ 源 many / 靶 one;改1:1两端 one ✅relation.spec.ts「表节点视觉」+relationEdges.test.ts - Frame helpers(包围盒 / 扩边 / 点落框 / 成员去重改名剔除 / renameFrame)✅
diagram.test.ts - schema
diagramFrame✅validate-projectjson.mjs - 公开 demo 主图节点 x 跨度更密(<1100)✅
demo.spec.ts+ 截图ux-walkthrough/demo-layout-density.png - 分享只读隐藏
relationNoShow(无del_flag)✅demo.spec.ts - dagre 默认间距 ≤ 旧走廊 80/160 ✅
graphLayout.test.ts - Frame 默认 padding 20(适应成员更贴表)✅
diagram.test.ts
Manual
- [分享页 Frame] 含
groups的项目分享 → 只读画布见虚线分组框 ✅demo.spec.ts - [Frame 主题色] demo Frame 底无 Ant 蓝;含 success
frameFill;截图demo-frame-theme-tokens.png✅demo.spec.ts+diagram.test.ts色板轮换 - [PK/FK 徽章扫读] 设计器/分享
.erd-pk-badge/.erd-fk-badge:≥10px/700、min-width≥22、warning/success;字段名 500/PK 600 不动;截图diagram-pk-fk-badge-hierarchy.png/demo-pk-fk-badge-hierarchy.png✅relation.specPK/FK +demo.spec - [边标签可读] 分享/设计器
erd-edge-label:白底 + ink900 + opacity=1 + ≥12px/600;截图demo-edge-label-chip.png✅demo.spec.ts+relation.specPK/FK - [边标签碰撞避让] 密图
erd-edge-label-nudge非零 + 标签 AABB 零重叠;截图demo-edge-label-collision.png✅relationEdges.test+demo.spec.ts - [边标签密度] chip padding ≤4/2、radius ≤3;碰撞盒跟字号(40×20)✅
relationEdges.test.ts+demo.spec/relation.spec表节点视觉 - [Frame 标题栏密度]
.erd-frame-chromeheight ≤22;截图demo-frame-theme-tokens.png✅demo.spec+diagram-frame.spec - [Frame 标题扫读] label ≥12/700 vs meta 更小/更轻 +
opacity < 1;padX≥8;双击重命名仍在;截图diagram-frame-title-hierarchy.png/demo-frame-title-hierarchy.png✅diagram-frame.spec「新建分组」+ 重命名 +demo.spec - [MiniMap sunk 对齐] 设计器/分享 MiniMap
backgroundColor= surfaceSunk#FAFBFC+ ≤128×96 紧凑;截图diagram-minimap-sunk.png✅relation.spec「MiniMap」+demo.spec - [MiniMap chrome margin] panel margin 8∈[8,12](禁 RF 默认 15);尺寸 128×96 保留概览;
getByRole('img', { name: '画布缩略图' });Controls/版本工具条/Auth 不弱化 ✅relation.spec「MiniMap」+demo.spec - [Controls / 工具栏 panel margin] Controls + 顶栏工具栏 margin 8∈[8,12](禁 RF 默认 15);对齐 MiniMap;
getByRole('button', { name: '适应画布' })/testid=canvas-toolbar✅relation.spec「Controls」+demo.spec - [基数 Select / Form mb] 编辑态 Select 高≤28;EntityModal 项 mb≤12 / 输入≤28 ✅
relation.spec「PK/FK」+「实体新建弹 层密度」 - [Controls 面板密度] 按钮 ≤22×22、面板
surface(禁 RF#fefefe);截图diagram-controls-dense.png✅relation.spec「Controls」+demo.spec - [Controls 扫读层次]「适应画布」
.erd-controls-primaryink900 + muted 底;图标 ≥12;aria 不变 ✅relation.spec「Controls」+demo.spec - [选中光晕统一] 表 / Frame
box-shadow环均为 brand a18(--erd-selection-ring);禁 Frame a12 ✅diagram-frame.spec「选中表→新建分组」+relation.spec「品牌 token」 - [画布工具栏密度]
.erd-canvas-toolheight ≤22、font ≤11;Panel margin 8;截图diagram-canvas-toolbar-dense.png✅relation.spec「工具栏」 - [画布工具栏扫读层次] 单块 chrome;次要 ink600;「自动布局」600/ink900;禁散粒描边 ✅
relation.spec「工具栏」 - [空态面板密度]
.erd-empty-ctapad ∈[8,12](目标 10×12)/ maxW≤300 / 标题≤14 / CTA hit ∈[26,28] / 剪影 compact≈112∈[96,120];截图diagram-empty-composition.png✅relation.spec「空态构图」 - [空态剪影 compact]
ErdEmptyDiagramcompact 112(原 132);禁 ≥132;≥96 保留存在感;Auth logo / 欢 迎 pad / hero 176 / Controls·工具栏 margin 不动 ✅relation.spec「空态构图」 - [空态 panel 顶距]
.erd-empty-panelmt ≈ min(8vh,64) 且 ∈[32,64];testid=canvas-empty-panel;禁 min(10vh,88);Auth logo / 欢迎 pad / CTA pad 不动 ✅relation.spec「空态构图」 - [空态纵节奏]
.erd-empty-titlemt≈8∈[6,10] /.erd-empty-descmb≈12∈[8,12];禁回退 16/18;Auth logo / 欢迎 pad / CTA pad / panel 顶距不动 ✅relation.spec「空态构图」 - [空态次链区 mt]
.erd-empty-linksmt≈10∈[8,12];testid=canvas-empty-links;Controls 已密不改;Auth logo / 欢迎 pad / CTA / panel / title·desc 不动 ✅relation.spec「空态构图」+「Controls」 - [命令面板密度] 面板宽≤460 / maxH≤360、输入高≤40 / font≤13、行 padY≤16 / font≤12、footer padY≤8 / padX≤8 / font≤11;截图
diagram-cmd-palette-dense.png✅relation.spec「命令面板」 - [命令面板空态/list 井] empty padY≤16 / padX≤8 / gap≤2、list pad≤2;无匹配「无匹配结果」+ 提示;Trap/aria/Esc 不弱化 ✅
relation.spec「命令面板」 - [实体新建弹层密度] 宽≤420、标题≤14·lh≥20、头/身/脚 pad≤8×12、表单项 margin≤14、输入/OK 高∈[28,32];截图
diagram-entity-modal-dense.png✅relation.spec「实体新建弹层密度 」 - [导入 Frame 建议] 前缀表 DBML → toast「已建议 N 个分组」+ 画布 2 个
diagram-frame(sys/biz);截图diagram-import-frame-suggest.png✅dbml-import.spec「前缀表」+suggestImportFrames.test/yarn test:unit:dbml - [拖入/出] 拖表中心进入空分组 → 成员+1 且框扩边;再拖出 → 成员-1(toast「已移出」)
Round 0 (2026-08-01)
Foundation slices before ReactFlow migration: smoke, datasource ACL, reverse import baseline.
Automated (yarn test:e2e / CI e2e-smoke)
- 登录页渲染;错误凭证停留登录页 ✅自动
- 登录 → 新建项目(4 必填字段)→ 列表可见 → 打开模型进设计器 → 删除清理 ✅自动
- VIP 计数缓存:删除项目后可再次创建(自清理用例隐含覆盖)✅自动
Manual / API assertions
- [自部署可观测]
GET /actuator/health→{"status":"UP"};GET /actuator/info→app.name=erd-online;未暴露/actuator/env→ HTTP 404(非假 500)✅curl 2026-08-02 - [自部署 DX 验收]
./scripts/verify-self-deploy.sh→ health/info/404/FE +flyway_schema_history有成功版本 ✅脚本 2026-08-02(ok=5) - [plaza Material 死码删除]
rg 'erd\.plaza|MaterialController' backend/src= 0;GET /material→ 404;health UP ✅ 2026-08-02 - [env 脚本补回] 全新克隆 →
yarn start直接可起 → 预期:8000 端口可访问,不报 env.local.sh 缺失 - [admin 权限种子补全] 全新初始化 DB → admin 登录进 /project/person → 预期:列表 200,无「权限不够」红提示
- [statistic 不含已删项目] 有软删除项目时
GET /ncnb/project/statistic→ 预期:total/personTotal 只计 del_flag=0,与 /project/recent 列表数一致(2026-08-01 curl 验证通过) - [VIP 计数缓存失效] 建项目 → 删除 → 立即再建 → 预期:不报「个人项目已超过1个」
Multi-DB reverse Dialect SPI (2026-08-01)
Per-dialect reverse parse (indexes, FKs, comments) and wizard UX — mix of unit tests, curl, and manual DB instances.
Automated
- IndexResultSetMapper:PRIMARY/统计行跳过、复合索引、STATISTICS LOWCASE ✅自动
- Registry:MySQL/MariaDB → Mysql;PostgreSQL → Postgresql;Oracle → Oracle;SQL Server → SqlServer;其余 → Generic ✅自动
-
dbReverseMetaMySQL:supportsSchema=false且schemas=[]✅curl - FK 映射:两端在表集内才产出、去重、字段 UPPERCASE ✅
ForeignKeyAssociationMapperTest - Colima MySQL
reverse_demo:indexs + associations 1:n ✅curl - Colima MySQL
reverse_demo:COLUMN_DEF→fields[].defaultValue(status='NEW'/amount=0.00/created_at=CURRENT_TIMESTAMP)✅curl +DefaultValueMapperTest - Colima PostgreSQL
reverse_demo:meta schemas=[public];indexs + associations ✅curl - Colima PostgreSQL
reverse_demo:表/列 COMMENT →entity.chnname/fields[].chnname(字典 obj/col_description)✅curl - Colima SQL Server(Azure SQL Edge):meta
supportsComment=true;MS_Description→entity.chnname/fields[].chnname(单测/fixture 已备;curl 待镜像可用) - Oracle 表/列注释:
ALL_TAB_COMMENTS/ALL_COL_COMMENTS→entity.chnname/fields[].chnname;metasupportsComment=true(无本机 Oracle:OracleReverseDialectCommentTest+ mapper)✅自动 - MySQL 表触发器:
INFORMATION_SCHEMA.TRIGGERS→entity.triggers[](name/timing/event/statement/ddl);metasupportsTrigger=true(MysqlReverseDialectTriggerTest+TriggerResultSetMapperTest)✅自动 - PostgreSQL 表触发器:
information_schema.triggers→entity.triggers[](name/timing/event/statement/双引号 ddl);metasupportsTrigger=true(PostgresqlReverseDialectTriggerTest+ mapper PG)✅自动 - SQL Server 表触发器:
sys.triggers/sys.trigger_events+OBJECT_DEFINITION→entity.triggers[]