diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb03825e..ef32e3ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,14 @@ jobs: - name: Install dependencies run: pnpm install --no-frozen-lockfile + - name: Restore ESLint cache + uses: actions/cache@v4 + with: + path: .eslintcache + key: eslint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.*') }} + restore-keys: | + eslint-${{ runner.os }}- + - name: Typecheck run: pnpm typecheck diff --git a/.gitignore b/.gitignore index a751acb0..203313a7 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ temp/ eslint-fix/ +.eslintcache remotion/* .home/ \ No newline at end of file diff --git a/package.json b/package.json index 3160aa01..c427b37a 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "dist:linux": "electron-builder --linux", "preview": "electron-vite preview", "typecheck": "tsc --noEmit", - "lint": "eslint src/", - "lint:fix": "eslint src/ --fix", + "lint": "eslint src/ --cache --cache-location .eslintcache --cache-strategy content", + "lint:fix": "eslint src/ --fix --cache --cache-location .eslintcache --cache-strategy content", "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css}\"", "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css}\"", "check": "pnpm typecheck && pnpm lint && pnpm test && pnpm build", diff --git a/src/main/services/team/TeamDataService.ts b/src/main/services/team/TeamDataService.ts index cedd89bd..e900e050 100644 --- a/src/main/services/team/TeamDataService.ts +++ b/src/main/services/team/TeamDataService.ts @@ -928,13 +928,9 @@ export class TeamDataService { await this.taskWriter.setNeedsClarification(teamName, taskId, null); } - if (task?.owner) { + if (task?.owner && !this.isLeadOwner(task.owner, leadName)) { // UX: don't echo a user comment as an inbox notification "from the lead" when the // task is already owned by the lead. This creates confusing self-notifications. - if (this.isLeadOwner(task.owner, leadName)) { - return comment; - } - const parts = [ `Comment on task #${taskId} "${task.subject}":\n\n${text}`, `\n${AGENT_BLOCK_OPEN}`, diff --git a/src/renderer/components/team/TeamProvisioningBanner.tsx b/src/renderer/components/team/TeamProvisioningBanner.tsx index 31eb02a0..d25ffc1f 100644 --- a/src/renderer/components/team/TeamProvisioningBanner.tsx +++ b/src/renderer/components/team/TeamProvisioningBanner.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import { Button } from '@renderer/components/ui/button'; import { useStore } from '@renderer/store'; diff --git a/src/renderer/components/team/members/MemberLogsTab.tsx b/src/renderer/components/team/members/MemberLogsTab.tsx index 2911d47c..0d72243c 100644 --- a/src/renderer/components/team/members/MemberLogsTab.tsx +++ b/src/renderer/components/team/members/MemberLogsTab.tsx @@ -107,7 +107,7 @@ export const MemberLogsTab = ({ cancelled = true; if (interval) clearInterval(interval); }; - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps -- intervalsKey drives refresh; deps intentionally minimal to avoid refetch loops }, [teamName, memberName, taskId, taskOwner, taskStatus, intervalsKey]); const handleExpand = useCallback( diff --git a/src/renderer/components/team/review/ChangeReviewDialog.tsx b/src/renderer/components/team/review/ChangeReviewDialog.tsx index efac7fb9..97adf069 100644 --- a/src/renderer/components/team/review/ChangeReviewDialog.tsx +++ b/src/renderer/components/team/review/ChangeReviewDialog.tsx @@ -185,6 +185,16 @@ export const ChangeReviewDialog = ({ [scrollToFile] ); + // Double rAF to ensure DOM/layout is ready before scrolling (reduces nesting in keydown handler) + const scheduleScrollToFile = useCallback( + (path: string) => { + requestAnimationFrame(() => { + requestAnimationFrame(() => scrollToFile(path)); + }); + }, + [scrollToFile] + ); + // Accept/Reject all across all files const handleAcceptAll = useCallback(() => { if (!activeChangeSet) return; @@ -629,9 +639,7 @@ export const ChangeReviewDialog = ({ }; addReviewFile(snap.file, { index: snap.index, content: restoredContent }); setActiveFilePath(snap.file.filePath); - requestAnimationFrame(() => { - requestAnimationFrame(() => scrollToFile(snap.file.filePath)); - }); + scheduleScrollToFile(snap.file.filePath); updateEditedContent(snap.file.filePath, snap.restoreContent); // Ensure editedContents is set before saveEditedFile reads it. void Promise.resolve().then(() => saveEditedFile(snap.file.filePath, projectPath)); @@ -687,7 +695,7 @@ export const ChangeReviewDialog = ({ updateEditedContent, saveEditedFile, projectPath, - scrollToFile, + scheduleScrollToFile, ]); // Cmd+N IPC listener (forwarded from main process) diff --git a/src/renderer/hooks/useChipDraftPersistence.ts b/src/renderer/hooks/useChipDraftPersistence.ts index b88b337a..e0c9b49b 100644 --- a/src/renderer/hooks/useChipDraftPersistence.ts +++ b/src/renderer/hooks/useChipDraftPersistence.ts @@ -52,7 +52,9 @@ export function useChipDraftPersistence(key: string): UseChipDraftResult { // without stale closures, using the same sync-ref pattern as keyRef. const chipsRef = useRef([]); - keyRef.current = key; + useEffect(() => { + keyRef.current = key; + }, [key]); // Load on mount useEffect(() => {