✨ feat(vscode): 看板默认显示团队工单,同事的卡只读并贴头像

看板范围默认从「只看我的」改为「团队全部」,同事移交或推进的工单不用
手动切开关就能看见。同事的卡片按 Gitea 归属(assignee 优先,无 assignee
退回创建者)判定为只读:禁止拖列、开会话、实施、合并分支、删 worktree、
删改工单,卡片右上角贴归属人头像,详情面板属性表整体消毒并显示只读徽章。
看 PR 改动、勾已查看、生成文件摘要属于本机审查动作,照常放行。

扩展侧在消息分发前统一拦截需要归属人身份的消息,兜住前端快照过期或工单
刚被重新指派的竞态;webhook 单卡上板时同步归属,避免增量卡漏进门禁。
头像由扩展侧带 token 抓回转 data URI 下发,绕开 webview CSP 对外链图片
的限制,按 URL 进程内缓存。

Claude-Session: https://claude.ai/code/session_01KSgcQwf1mWrb8qeTKFvJbV
This commit is contained in:
cruldra committed 2026-09-10 15:30:02 +08:00
1 parent e8963ac2a2
commit 9167b86708
26 files changed
+550 -34

No files matched your search

+15 -2
View File
@@ -137,6 +137,8 @@ export interface UseIssuesResult {
/** 看板范围:'mine' 只看我的(assigned/created),'all' 团队全部。 */
scope: 'mine' | 'all'
toggleScope: () => void
/** Gitea login → 头像 data URI;同事工单的卡片按它贴头像。 */
avatars: Record<string, string>
/**
* Latest known global `autoReview` value pushed alongside `issues/update`.
* Used by the detail panel to display the fallback value when an issue has
@@ -293,10 +295,15 @@ export interface UseIssuesResult {
isHandoffRunning: (issueNumber: number) => boolean
}
function isAvatarMap(raw: unknown): raw is Record<string, string> {
return !!raw && typeof raw === 'object' && Object.values(raw).every(v => typeof v === 'string')
}
function readInjectedSnapshot(): {
issues: Issue[]
scope: 'mine' | 'all'
me?: string
avatars?: Record<string, string>
globalAutoReview: boolean
youtrackConfigured: boolean
} | null {
@@ -316,6 +323,7 @@ function readInjectedSnapshot(): {
issues: obj.issues as Issue[],
scope: obj.scope,
me: typeof obj.me === 'string' ? obj.me : undefined,
avatars: isAvatarMap(obj.avatars) ? obj.avatars : undefined,
globalAutoReview: obj.globalAutoReview === true,
youtrackConfigured: obj.youtrackConfigured === true,
}
@@ -333,7 +341,8 @@ export function useIssues(): UseIssuesResult {
const [settings, setSettings] = useState<SettingsOverlayState | null>(null)
const [youtrackProjects, setYoutrackProjects] = useState<YouTrackProjectsState>({ status: 'idle', projects: [] })
const [globalAutoReview, setGlobalAutoReview] = useState<boolean>(injected?.globalAutoReview ?? true)
const [scope, setScope] = useState<'mine' | 'all'>(injected?.scope ?? 'mine')
// 默认团队全部,与扩展侧 DEFAULT_KANBAN_SCOPE 一致(两个包不共享源码)。
const [scope, setScope] = useState<'mine' | 'all'>(injected?.scope ?? 'all')
const [youtrackConfigured, setYoutrackConfigured] = useState<boolean>(injected?.youtrackConfigured ?? false)
const [toasts, setToasts] = useState<ToastItem[]>([])
const [profiles, setProfiles] = useState<ClaudeProfile[]>([])
@@ -350,6 +359,7 @@ export function useIssues(): UseIssuesResult {
const prDiffSummaryRunningRef = useRef<Set<number>>(new Set())
const [prDiffSummaryRunning, setPrDiffSummaryRunning] = useState<ReadonlySet<number>>(() => new Set())
const [me, setMe] = useState<string | undefined>(injected?.me)
const [avatars, setAvatars] = useState<Record<string, string>>(injected?.avatars ?? {})
const [handoffUsers, setHandoffUsers] = useState<{ issueNumber: number, users: string[] } | null>(null)
const handoffRunningRef = useRef<Set<number>>(new Set())
const [handoffRunning, setHandoffRunning] = useState<Set<number>>(new Set())
@@ -698,6 +708,9 @@ export function useIssues(): UseIssuesResult {
setGlobalAutoReview(msg.globalAutoReview)
setYoutrackConfigured(msg.youtrackConfigured)
setMe(msg.me)
// 头像抓取失败时下发空表,保留上一批已有的头像,避免卡片头像闪掉。
if (msg.avatars && Object.keys(msg.avatars).length > 0)
setAvatars(msg.avatars)
break
case 'issues/error':
setState({ status: 'error', message: msg.message })
@@ -895,5 +908,5 @@ export function useIssues(): UseIssuesResult {
return cleanup
}, [clearPrDiffSummaryRunning, markHandoffRunning])
return { state, settings, globalAutoReview, youtrackConfigured, importYouTrack, toasts, profiles, setIssues, refresh, scope, toggleScope, saveSettings, listYouTrackProjects, youtrackProjects, dismissSettings, requestEditAuth, createIssue, dismissToast, openUrl, resumeSession, resumeReviewSession, startTestSession, resumeTestSession, focusSession, openFile, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueProfilePath, updateIssueBrainstormProfilePath, updateIssueTestProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning }
return { state, settings, avatars, globalAutoReview, youtrackConfigured, importYouTrack, toasts, profiles, setIssues, refresh, scope, toggleScope, saveSettings, listYouTrackProjects, youtrackProjects, dismissSettings, requestEditAuth, createIssue, dismissToast, openUrl, resumeSession, resumeReviewSession, startTestSession, resumeTestSession, focusSession, openFile, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueProfilePath, updateIssueBrainstormProfilePath, updateIssueTestProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning }
}