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/`.
|
**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
|
## RBAC & Permissions
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ export default function AssistantChatPage() {
|
|||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
const dragCounterRef = useRef(0)
|
const dragCounterRef = useRef(0)
|
||||||
const prefillHandledRef = useRef(false)
|
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
|
// Persist active chat ID to sessionStorage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -214,6 +217,7 @@ export default function AssistantChatPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectChat = useCallback(async (chatId: string) => {
|
const selectChat = useCallback(async (chatId: string) => {
|
||||||
|
currentChatRef.current = chatId
|
||||||
setActiveChatId(chatId)
|
setActiveChatId(chatId)
|
||||||
// Clear TaskLane when switching chats — will restore from backend if available
|
// Clear TaskLane when switching chats — will restore from backend if available
|
||||||
setShowTaskLane(false)
|
setShowTaskLane(false)
|
||||||
@@ -221,6 +225,10 @@ export default function AssistantChatPage() {
|
|||||||
setActiveActions([])
|
setActiveActions([])
|
||||||
try {
|
try {
|
||||||
const detail = await aiSessionsApi.getSession(chatId)
|
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(
|
setMessages(
|
||||||
(detail.conversation_messages || []).map(m => ({
|
(detail.conversation_messages || []).map(m => ({
|
||||||
role: m.role as 'user' | 'assistant',
|
role: m.role as 'user' | 'assistant',
|
||||||
@@ -264,6 +272,7 @@ export default function AssistantChatPage() {
|
|||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
|
currentChatRef.current = session.session_id
|
||||||
setChats(prev => [chatItem, ...prev])
|
setChats(prev => [chatItem, ...prev])
|
||||||
setActiveChatId(session.session_id)
|
setActiveChatId(session.session_id)
|
||||||
setMessages([])
|
setMessages([])
|
||||||
@@ -430,6 +439,7 @@ export default function AssistantChatPage() {
|
|||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
|
currentChatRef.current = session.session_id
|
||||||
setChats(prev => [chatItem, ...prev])
|
setChats(prev => [chatItem, ...prev])
|
||||||
setActiveChatId(session.session_id)
|
setActiveChatId(session.session_id)
|
||||||
setMessages([{ role: 'user', content: resumePrompt }])
|
setMessages([{ role: 'user', content: resumePrompt }])
|
||||||
|
|||||||
Reference in New Issue
Block a user