feat(access): Identities 操作 modal + <Can> 权限门 (PR-10) - #29
Merged
Merged
Conversation
Batch 2 · Access · Identities 第二段(操作;#13 · T203 · T205),stacked 于 PR-09。 - api/identities:create/patch/delete/lock/unlock/changeUserPassword (edit 走 PATCH,是 email-only PUT update 的超集,UI 不另用 update) - useIdentitiesStore:create/edit/lock/unlock/remove/changePassword mutation actions — 失败 re-throw(modal 内联展示并保持打开),成功 await fetchList()(changePassword 不 refetch) - components/ModalShell:a11y modal 原语(hand-rolled focus-trap + ESC + backdrop + 焦点回归;jsdom 无 HTMLDialogElement.showModal);第二个 cell 复用时再上提 @gocell/core - components/IdentityFormModal(create/edit)· ChangePasswordModal · ConfirmDialog(lock/unlock/delete,role=alertdialog) - lib/identityValidation:create/edit/change-password 校验(复用 validation.ts 的 EMAIL_RE/USERNAME_RE/pwChecks,返回 i18n key) - IdentitiesView:行操作列 + Create 按钮,每个动作挂 <Can>(fail-closed 隐藏);wire 三个 modal - apps/web 路由 /access/identities 挂 meta.requiredAction='read' + requiredResource='identity'(guards.ts PDP fail-closed gate) - core i18n:access.identities.{actions,form,password,confirm}.* 双语 (email 占位符 @ 转义;规避 vue-i18n linked-message trap) access 194 通过、整包 ~97.5% lines;workspace 511 通过;contracts clean。 Closes #13 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6-reviewer 审查的 IN_SCOPE Cx1/Cx2 修复: - a11y 对比度(P1):新增 themed `--err-strong` token(light 0.47 / dark 0.72), 危险按钮改 `color:var(--bg)`+`bg:var(--err-strong)`(随主题反色,两套主题都过 WCAG AA),危险行操作文字改 `--err-strong`(旧 `--err` 在白底仅 ~3.3:1) - a11y(P1):ConfirmDialog role=alertdialog 补 `aria-describedby` → message; ModalShell 新增 `descriptionId` prop - ModalShell 加固:watch `flush:'post'`(消除开模一帧内 Tab 逃逸的 listener 时序 缝隙);Tab-trap 用 indexOf,panel/外部焦点(idx=-1)也环绕,焦点不再逃出; opener 用 `instanceof HTMLElement`;`els[0]!`/`els.at(-1)!` 去除 as 转型; FOCUSABLE 选择器补 summary/contenteditable - ChangePasswordModal:校验收进 computed(对齐 IdentityFormModal,去掉每次渲染 3× 重复校验) - IdentitiesView:onConfirm 改显式三分支(lock/unlock/delete),去掉 catch-all fallthrough(防未来新增 action 静默落到 remove) - IdentityFormModal:requirePasswordReset checkbox 补 id 未处理(→ 人工确认,见 PR/收尾):core 内业务 i18n(pre-existing,需 i18n 联邦 化)、ModalShell 上提 @gocell/core(文档化的"第二 cell 再提")、乐观更新替代 全量 refetch、背景 inert、tab 方向键、z-index/hover token 化、useId 多实例、 逐行 aria-label。 access 194 通过;typecheck/lint 全绿;覆盖率 ~97.4%(ModalShell 98.6%)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…保留 + a11y/token 收口 应"能处理的就处理好",把可干净修复的 deferred 项落地(i18n 联邦化单独 PR): - [#2 架构] ModalShell 从 @gocell/access 上提到 @gocell/core/components(纯 UI 原语, 无业务);三个 modal 改 import { ModalShell } from '@gocell/core/components'; spec 一并迁移到 core。core ./components 已有 exports,无新增入口。 - [#4 a11y] ModalShell 开启时对背景兄弟节点逐级 inert(panel→body 跳过自身链路), 补 aria-modal 在非原生 dialog 下 NVDA/JAWS 虚拟光标可达背景的缺口;关闭/卸载还原。 - [#3 bug] useIdentitiesStore 新增 refreshLoaded():mutation 成功后按"已加载条数" 重取(而非塌回第 1 页),不再丢失 loadMore 展开的页;新增 100 行场景测试。 - [#5 a11y] 行操作按钮加 username 作用域 aria-label(actionLabel 助手 + access.identities.actions.rowSuffix i18n),读屏不再"编辑/编辑/编辑"。 - [#6 token] 新增 --z-modal / --fg-hover token;ModalShell z-index 与 create/ primary 按钮 hover 改用 token(去 z-index 魔法数 + oklch 字面量,PR-10 新代码)。 - [#7 复用] ConfirmDialog 加 cancelKey prop(默认 access.identities.confirm.cancel), 解除与 identities 命名空间的耦合。 未做(理由):i18n 联邦化(#1,唯一被核查判 should-fix)—— 需迁移已合并的 PR-08 login/firstRun key + 改 apps/web i18n bootstrap,属独立横切重构,单独开 PR;useId 唯一 title-id(无现存 bug、单实例,且会冲掉 4 个 spec 的 #id 选择器)。 core 193 / access 186 通过;workspace 515 通过(useTheme 为既有并发 flake,隔离通过); typecheck/lint 全绿;contracts 未触碰(codegen 另现 audit schema 上游漂移,与本 PR 无关, 属独立 codegen-sync)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # packages/access/src/views/IdentitiesView.vue # packages/core/src/i18n/messages/en-US.ts # packages/core/src/i18n/messages/zh-CN.ts
/pr-review 29(6-reviewer 六维度)IN_SCOPE 修复: - 设计/bug(P1):ChangePasswordModal `.cp__btn--primary` 与 ConfirmDialog `.confirm__btn--primary` / `--danger` 缺 hover 覆盖 → 被通用 :hover 的 --bg-sunken 冲掉,primary 变「白字白底」不可见、danger 丢失红色。补 variant hover(primary→--fg-hover,danger→--err-strong + brightness) - perf(P1):IdentitiesView formatDate 把 Intl.DateTimeFormat 提到 setup 级 单例(原每行每渲染重建,构造成本 ~10×) - TS(P1):IdentityFormModal/ChangePasswordModal fieldError 返回 string(''=无), 去掉模板 6 处 `as string` 强转 - TS(P1):onConfirm 补 else 的 never 穷举守门(未来新增 ConfirmAction 编译期报错) - a11y(P1):行操作按钮 height 28px → min-height 30px(PRD 数字基线 30px 例外) - a11y(P2):requirePasswordReset checkbox label 补 for(消除未用 id) - 设计(P2):tokens [data-theme=dark] 补 --fg-hover(暗色改为变暗,否则 near-white → white hover 不可感知) 未做(理由):tab 方向键漫游(仅 1 个启用 tab,退化 tablist,待 Wave 2 服务账号 启用再补);role=alert 同次重复提交不重播(错误可见且首次已播报,edge);resultCount 英文复数(sr-only,vue-i18n 复数 API 风险 > 收益);ModalShell/Can tree-shaking (Can 在 access 同被使用,无实际影响);useId 唯一 id(单实例无冲突);i18n 业务 key 在 core(既有,独立联邦化 PR)。 access 188 / core 193 通过;typecheck / lint 全绿。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Batch 2 · Access · Identities 的第二段(PR-10,操作) — stacked 于 #28 (PR-09),base 为
009-b2-identities-list。覆盖 #13 的 T203(create/edit/change-password modal + lock/unlock)· T205(动作挂<Can>+ 路由meta.requiredAction)。做了什么
createUser(POST) ·patchUser(PATCH) ·deleteUser·lockUser·unlockUser·changeUserPassword(POST/users/{id}/password)。edit 走 PATCH(email-only PUTupdate的超集,UI 不另用 update)。create/edit/lock/unlock/remove/changePasswordmutation actions —— 与读操作相反,失败 re-throw(modal 内联展示并保持打开),成功后await fetchList()(changePassword不 refetch)。HTMLDialogElement.showModal,故不用原生<dialog>;第二个 cell 复用时上提@gocell/core。role="alertdialog")。validation.ts的EMAIL_RE/USERNAME_RE/pwChecks,返回 i18n key。<Can>(fail-closed 隐藏);wire 三个 modal。/access/identities挂meta.requiredAction='read'+requiredResource='identity'→guards.tsPDP fail-closed gate(PDP 后端未接通前整页拒绝,BR-004;设计的安全姿态)。语义提示
change-password契约要oldPassword+newPassword且返回 token pair(轮换会话)→ 自助改密语义;管理员强制重置走patch{requirePasswordReset:true}。涉及包
@gocell/access:api/store/3 modals + ModalShell/validators/View(无新exports——modal/原语均包内私有)@gocell/core:access.identities.{actions,form,password,confirm}.*i18n(zh-CN + en-US;@已转义规避 vue-i18n linked-message trap)apps/web:路由meta.requiredActiona11y
ModalShell:
role=dialog/alertdialog+aria-modal+aria-labelledby+ focus-trap(Tab/Shift+Tab wrap)+ ESC + 焦点回归;表单<label for>+aria-invalid+aria-describedby+role="alert"。Test plan
pnpm -F @gocell/access test(194 通过)pnpm -w typecheck(11 包)·pnpm -w lint(0)·pnpm -w test(37 files / 511 通过)pnpm codegen && git diff --exit-code packages/contracts/src/(clean)Closes #13
ref: antdv-pro/antdv-pro src/pages/list/components/crud-table-modal.vue
ref: W3C WAI-ARIA APG dialog-modal pattern
🤖 Generated with Claude Code