From 09c6ac41e043e42443d8c63cb7973cf374417f4a Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 14 Oct 2026 02:51:51 +0000 Subject: [PATCH] Simplify the settled project settings implementation Why: Keep the completed settings flows readable without adding unused API exports or duplicate helpers. Evidence: SELF /tmp/project-settings-simplify-green-20261014-973d52c1.json records settings tests/vet, 284 Frontend tests, typecheck, the unchanged knip baseline and diff checks. Reuse and quality passes were independent read-only agents; efficiency was reviewed inline after the host rejected another review thread. Impact: Preserve scope rules, write guards, DTOs, errors and ordering. Reuse Core ownership constants and path lookup; remove unused exports and keep permission selection in a typed local state. Refs: P1/P7, docs/records/2026-10-14-rdm-settings-completion-contract.md --- src/components/RdmDutiesSection.tsx | 4 ++-- src/components/RdmPermMatrixSection.tsx | 25 ++++++++++++------------- src/components/SettingsFields.tsx | 8 ++++---- src/components/SettingsModal.tsx | 2 +- src/components/rdmSettingsShared.tsx | 2 +- src/rdmManagement.ts | 2 +- 6 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/components/RdmDutiesSection.tsx b/src/components/RdmDutiesSection.tsx index 3473f2c..5f58d55 100644 --- a/src/components/RdmDutiesSection.tsx +++ b/src/components/RdmDutiesSection.tsx @@ -8,7 +8,7 @@ import { assignDocDutiesPayload, docApprovePayload, docFinalPayload, docRejectPa import { Feedback, personItem, projectContext, readableError, RoleFilter, selectedPeople, useRequestFence, useRoleOptions, validVersion, type MemberList, type Person } from './rdmSettingsShared' -export interface Duties { author: string; reviewers: string[]; approver: string } +interface Duties { author: string; reviewers: string[]; approver: string } type WireDuties = Partial & { Author?: string; Reviewers?: string[]; Approver?: string } export function normalizeDuties(value?: WireDuties | null): Duties { return { author: value?.author ?? value?.Author ?? '', reviewers: value?.reviewers ?? value?.Reviewers ?? [], approver: value?.approver ?? value?.Approver ?? '' } @@ -26,7 +26,7 @@ interface CatalogDoc { sections: number; assigned: boolean; author?: string; reviewers?: string[]; approver?: string activeRound?: number; activeSections?: number; status?: string; statusLabel?: string; dutyVersion?: number } -export interface DutyTreeNode { +interface DutyTreeNode { id: string; kind: 'stage' | 'type' | 'doc' | 'section'; label: string; selectable: boolean docId?: string; sectionId?: string; children?: DutyTreeNode[] } diff --git a/src/components/RdmPermMatrixSection.tsx b/src/components/RdmPermMatrixSection.tsx index 58fa57f..2e7f29c 100644 --- a/src/components/RdmPermMatrixSection.tsx +++ b/src/components/RdmPermMatrixSection.tsx @@ -71,7 +71,7 @@ export function RdmPermMatrixSection({ project }: { project: Project }) { const [roleId, setRoleId] = useState('') const [projectRole, setProjectRole] = useState('') const [group, setGroup] = useState('all') - const [expanded, setExpanded] = useState('') + const [expanded, setExpanded] = useState<{ member: string; action: string } | null>(null) const [busy, setBusy] = useState(false) const [loading, setLoading] = useState(false) const [error, setError] = useState('') @@ -106,12 +106,12 @@ export function RdmPermMatrixSection({ project }: { project: Project }) { } useEffect(() => { fence.invalidate(); setView('member'); setMember(me?.owner || ''); setBatch(null); setMatrix(null) - setCanConfigure(false); setExpanded(''); setQuery(''); setRoleId(''); setProjectRole(''); setBusy(false); setNotice(''); setError('') + setCanConfigure(false); setExpanded(null); setQuery(''); setRoleId(''); setProjectRole(''); setBusy(false); setNotice(''); setError('') void queryMember(me?.owner || '') }, [context]) useEffect(() => { if (view !== 'matrix' || !canConfigure) return - fence.cancel('read'); setExpanded('') + fence.cancel('read'); setExpanded(null) const timer = setTimeout(() => { void queryBatch() }, query ? 250 : 0) return () => { clearTimeout(timer); fence.cancel('read') } }, [view, query, roleId, projectRole, canConfigure]) @@ -141,21 +141,20 @@ export function RdmPermMatrixSection({ project }: { project: Project }) { } const visible = (actions: MatrixAction[]) => projectActions(actions).filter(action => group === 'all' || actionGroup(action.action) === group) const matrixActions = [...new Map((batch?.rows ?? []).flatMap(row => visible(row.actions)).map(action => [action.action, action])).values()] - const expandedOwner = expanded.split('\u0000')[0] - const expandedAction = expanded.split('\u0000')[1] + const expandedRow = expanded ? batch?.rows.find(row => row.member === expanded.member) : undefined return
- { setGroup(e.target.value); setExpanded(null) }}> - { fence.cancel('read'); setRoleId(value); setExpanded('') }} /> - { fence.cancel('read'); setProjectRole(e.target.value); setExpanded(null) }}> {view === 'member' && { const permission = row.actions.find(item => item.action === action.action && item.scope === 'project') return {permission ? : '未核验'} + onClick={() => setExpanded({ member: row.member, action: action.action })}>{permission.allowed ? '允许' : '禁止'} : '未核验'} })})}
{batch.rows.length === 0 &&

没有匹配的项目成员

} {batch.next && queryBatch(batch.next)}>加载更多成员} - {expanded &&

{batch.rows.find(row => row.member === expandedOwner)?.name || expandedOwner} · 权限依据

- row.member === expandedOwner)?.actions.filter(action => action.action === expandedAction) || []} - canManage={canConfigure && !writeError} busy={busy} onOverride={(action, mode) => override(expandedOwner, action, mode)} />
} + {expanded &&

{expandedRow?.name || expanded.member} · 权限依据

+ action.action === expanded.action) || []} + canManage={canConfigure && !writeError} busy={busy} onOverride={(action, mode) => override(expanded.member, action, mode)} />
} }
diff --git a/src/components/SettingsFields.tsx b/src/components/SettingsFields.tsx index 0594d45..1511664 100644 --- a/src/components/SettingsFields.tsx +++ b/src/components/SettingsFields.tsx @@ -1,16 +1,16 @@ import { Btn, SettingsGroup, SettingRow } from '@ui' import type { SettingDesc } from '../contracts' -export const groupNames: Record = { +const groupNames: Record = { models: '模型与密钥', runtime: '运行环境', context: '上下文', retrieval: '检索', ui: '界面', plugins: '系统插件', crossSession: '个人会话协作', tools: '工具服务', deliverables: '项目交付规则', telemetry: '运行观测', } -export const optionLabels: Record = { modern: '现代', formal: '正式', minimal: '简洁', +const optionLabels: Record = { modern: '现代', formal: '正式', minimal: '简洁', dark: '深色', light: '浅色', system: '跟随系统', zh: '中文', en: '英文', auto: '自动', off: '关闭', on: '开启', disabled: '关闭', enabled: '开启', relevant: '相关会话', all: '全部本人会话', none: '关闭', direct: '直接对话', squad: '协作小队', zhCN: '简体中文', 'zh-CN': '简体中文', 'en-US': '英语', } -export function readableValue(value: unknown) { +function readableValue(value: unknown) { if (value === true) return '开启' if (value === false) return '关闭' if (value === undefined || value === null || value === '') return '未配置' @@ -62,7 +62,7 @@ export function flatten(map: Record): Record { } visit(map); return out } -export function unflatten(map: Record, preserveEmpty = false): Record { +function unflatten(map: Record, preserveEmpty = false): Record { const out: Record = {} for (const [path, value] of Object.entries(map)) { if (value === undefined || (value === '' && !preserveEmpty)) continue diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index c0cede2..e7df305 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -7,7 +7,7 @@ import { BorrowPanels } from './BorrowPanels' import { flatten, scopedDraft, scopedSchema, SettingsFields, personalSettingsMutation } from './SettingsFields' import { Feedback, readableError, useRequestFence } from './rdmSettingsShared' -export { SettingInput, settingInputAttrs, flatten, unflatten, groupNames } from './SettingsFields' +export { SettingInput, settingInputAttrs } from './SettingsFields' export function SettingsModal() { const owner = useStore(s => s.me?.owner) diff --git a/src/components/rdmSettingsShared.tsx b/src/components/rdmSettingsShared.tsx index 0a35c22..28d1b42 100644 --- a/src/components/rdmSettingsShared.tsx +++ b/src/components/rdmSettingsShared.tsx @@ -14,7 +14,7 @@ export interface MemberList { members: Person[]; next?: string; count?: number; accountDirectoryAvailable?: boolean canManageMembers?: boolean; canAssignDuties?: boolean; canConfigurePermissions?: boolean } -export interface RoleOption { roleId: string; name: string } +interface RoleOption { roleId: string; name: string } export const projectRoleLabel = (role?: string) => role === 'owner' ? '项目负责人' : role === 'member' ? '项目成员' : '未加入项目' export const personLabel = (person: Person) => person.name || person.owner export function personItem(person: Person, adding = false): PickItem { diff --git a/src/rdmManagement.ts b/src/rdmManagement.ts index f9d4b9c..f9753dd 100644 --- a/src/rdmManagement.ts +++ b/src/rdmManagement.ts @@ -146,7 +146,7 @@ export function describeSource(source: string): string { } } -export const ACTION_LABELS: Record = { +const ACTION_LABELS: Record = { 'project.create': '创建项目', 'project.archive': '归档项目', 'member.manage': '管理项目成员', 'duty.assign': '分配文档职责', 'stage.advance': '推进项目阶段', 'doc.read': '读取文档', 'doc.write': '编制与编辑文档', 'doc.publish': '发布受控文档', 'review.participate': '参与文档审签',