refactor: account settings page audit — tokens, a11y, hierarchy #123
@@ -373,7 +373,7 @@ gh run view <id> --json jobs --jq '.jobs[] | {name: .name, conclusion: .conclusi
|
||||
|
||||
**105. `npm run build` fails with `EACCES: permission denied` on `dist/` in code-server:** This is a filesystem permission issue in the Docker environment, not a TypeScript error — the TS compilation completes successfully. Use `npx tsc -b` to verify TypeScript cleanly without needing to write to `dist/`.
|
||||
|
||||
---
|
||||
**106. Guard async "select item → load data → apply state" flows with a ref:** When a component lets the user switch between items (chat sessions, flows, scripts) and loads data asynchronously on each switch, the load for item A can complete *after* the user has already switched to item B — overwriting B's state with A's stale data. Fix pattern: keep a `currentSelectionRef = useRef(initialId)` and update it synchronously whenever the selection changes (in every creation/switch path). After every `await`, bail out if `currentSelectionRef.current !== thisItemId`. See `AssistantChatPage.tsx` `selectChat` for the reference implementation (`currentChatRef`).
|
||||
|
||||
## RBAC & Permissions
|
||||
|
||||
|
||||
@@ -81,6 +81,9 @@ export default function AssistantChatPage() {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const dragCounterRef = useRef(0)
|
||||
const prefillHandledRef = useRef(false)
|
||||
// Tracks the most recently requested active chat ID so in-flight selectChat
|
||||
// calls that complete after the user switches chats don't clobber new state.
|
||||
const currentChatRef = useRef<string | null>(activeChatId)
|
||||
|
||||
// Persist active chat ID to sessionStorage
|
||||
useEffect(() => {
|
||||
@@ -214,6 +217,7 @@ export default function AssistantChatPage() {
|
||||
}
|
||||
|
||||
const selectChat = useCallback(async (chatId: string) => {
|
||||
currentChatRef.current = chatId
|
||||
setActiveChatId(chatId)
|
||||
// Clear TaskLane when switching chats — will restore from backend if available
|
||||
setShowTaskLane(false)
|
||||
@@ -221,6 +225,10 @@ export default function AssistantChatPage() {
|
||||
setActiveActions([])
|
||||
try {
|
||||
const detail = await aiSessionsApi.getSession(chatId)
|
||||
// Guard: if the user switched to a different chat while this API call was
|
||||
// in flight (e.g. clicked "New Chat"), discard stale results so we don't
|
||||
// clobber the new session's task lane state.
|
||||
if (currentChatRef.current !== chatId) return
|
||||
setMessages(
|
||||
(detail.conversation_messages || []).map(m => ({
|
||||
role: m.role as 'user' | 'assistant',
|
||||
@@ -264,6 +272,7 @@ export default function AssistantChatPage() {
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
currentChatRef.current = session.session_id
|
||||
setChats(prev => [chatItem, ...prev])
|
||||
setActiveChatId(session.session_id)
|
||||
setMessages([])
|
||||
@@ -430,6 +439,7 @@ export default function AssistantChatPage() {
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
currentChatRef.current = session.session_id
|
||||
setChats(prev => [chatItem, ...prev])
|
||||
setActiveChatId(session.session_id)
|
||||
setMessages([{ role: 'user', content: resumePrompt }])
|
||||
|
||||
Reference in New Issue
Block a user