diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx index 93015281..87ae2dbb 100644 --- a/frontend/src/app.tsx +++ b/frontend/src/app.tsx @@ -101,11 +101,9 @@ function PublicLoginLayout() { ); } -// Root layout for the data router. Everything that previously sat between -// `` and `` (providers, Suspense) lives here so it has -// access to router hooks (`useNavigate`, `useLocation`, ...) while still being -// rendered under the data router. This is what enables `useBlocker` and other -// data-router-only features inside our pages. +// Providers + Suspense live inside the data router (not wrapping `RouterProvider`) so +// they can use router hooks (`useNavigate`, `useLocation`) and enable `useBlocker` and +// other data-router-only features inside our pages. function RootLayout() { return ( @@ -133,9 +131,7 @@ const router = createBrowserRouter( element={} errorElement={} > - {/* private routes */} }> - {/* Main layout for chat pages */} }> } @@ -143,7 +139,6 @@ const router = createBrowserRouter( path="dashboard" /> - {/* Flows section with FlowsProvider */} }> } @@ -193,7 +188,6 @@ const router = createBrowserRouter( /> - {/* Settings with nested routes */} } path="settings" @@ -250,14 +244,12 @@ const router = createBrowserRouter( - {/* report routes */} } handle={routeTitles.flowReport} path="flows/:flowId/report" /> - {/* public routes */} } handle={routeTitles.login} @@ -270,7 +262,6 @@ const router = createBrowserRouter( path="oauth/result" /> - {/* other routes */} } path="/" diff --git a/frontend/src/components/dashboard/chart-tooltip.tsx b/frontend/src/components/dashboard/chart-tooltip.tsx index 0c511ccc..448da590 100644 --- a/frontend/src/components/dashboard/chart-tooltip.tsx +++ b/frontend/src/components/dashboard/chart-tooltip.tsx @@ -33,12 +33,10 @@ export function ChartTooltip({ const shownInSessionRef = useRef(false); - // New mouse-entry session: reset "already shown" tracking useEffect(() => { shownInSessionRef.current = false; }, [sessionKey]); - // Notify parent the moment the tooltip first becomes visible in this session useEffect(() => { if (active && !shownInSessionRef.current) { shownInSessionRef.current = true; diff --git a/frontend/src/components/shared/confirmation-dialog.tsx b/frontend/src/components/shared/confirmation-dialog.tsx index 1ec2bacb..6c87d0a3 100644 --- a/frontend/src/components/shared/confirmation-dialog.tsx +++ b/frontend/src/components/shared/confirmation-dialog.tsx @@ -50,9 +50,8 @@ function ConfirmationDialog({ }: ConfirmationDialogProps) { const [isProcessing, setIsProcessing] = useState(false); - // Derive a contextual title from confirm verb + item type so callers don't - // see "Confirm Action" for a Delete prompt or a Save prompt. Explicit - // `title` always wins. + // `verb !== 'Confirm'` treats the default confirmText as "no custom verb": a bare + // Confirm gets the generic "Confirm Action" title instead of "Confirm ". const verb = confirmText.trim(); const resolvedTitle = title ?? (verb && verb !== 'Confirm' ? `${verb} ${itemType}` : 'Confirm Action'); diff --git a/frontend/src/components/shared/detail-navigation/use-detail-navigation.ts b/frontend/src/components/shared/detail-navigation/use-detail-navigation.ts index dfeef411..70e5f58c 100644 --- a/frontend/src/components/shared/detail-navigation/use-detail-navigation.ts +++ b/frontend/src/components/shared/detail-navigation/use-detail-navigation.ts @@ -294,9 +294,8 @@ export function useDetailNavigation({ const handleItemSelect = useCallback( (item: T) => { - // Close the sheet *before* navigating — preserves the pre-refactor - // ordering so a route change can't unmount the sheet while its - // close callback is still in flight. + // Close the sheet *before* navigating so a route change can't unmount + // the sheet while its close callback is still in flight. setSheetOpen(false); navigate(buildHref(item), { replace: true }); }, diff --git a/frontend/src/components/shared/document-title.test.tsx b/frontend/src/components/shared/document-title.test.tsx index be9ab9e7..27f36050 100644 --- a/frontend/src/components/shared/document-title.test.tsx +++ b/frontend/src/components/shared/document-title.test.tsx @@ -18,7 +18,6 @@ describe('DocumentTitle', () => { it('renders APP_NAME when no matched route exposes a title handle', async () => { renderAt('/anywhere', [ { - // No child route handle — DocumentTitle should fall back to APP_NAME only. children: [{ element: page, path: 'anywhere' }], element: ( <> @@ -145,8 +144,6 @@ describe('DocumentTitle', () => { }); it('treats an unmarked function as a plain resolver, not a component', async () => { - // Without the marker, DocumentTitle calls the function with params and - // wraps the returned string with the standard "X — PentAGI" template. const resolveTitle = (params: Record) => `Item ${params.id}`; renderAt('/items/7', [ @@ -179,9 +176,8 @@ describe('DocumentTitle', () => { }, ]); - // An empty string from the resolver is treated as "no title" — fall back - // to APP_NAME alone. This guards the route-level convention: pages that - // do not want a prefix can return '' instead of omitting the handle. + // Route-level convention: pages that don't want a title prefix return '' + // intentionally, rather than omitting the handle. await waitFor(() => expect(document.title).toBe('PentAGI')); }); }); diff --git a/frontend/src/components/shared/file-manager/file-manager-utils.ts b/frontend/src/components/shared/file-manager/file-manager-utils.ts index c6786aa2..0e50d56c 100644 --- a/frontend/src/components/shared/file-manager/file-manager-utils.ts +++ b/frontend/src/components/shared/file-manager/file-manager-utils.ts @@ -270,8 +270,7 @@ const ALWAYS = (): true => true; /** * Single DFS walker that powers all `collect*` helpers. Pure: returns a freshly - * allocated array. Internally uses a private accumulator to avoid `concat` - * allocations on deep trees, but the buffer is never exposed to callers. + * allocated array. */ export const walkTree = ( nodes: FileManagerInternalNode[], @@ -361,7 +360,6 @@ export const collectDirectoryPaths = (nodes: FileManagerInternalNode[]): string[ include: (node) => node.isDir, }); -/** Locale-aware case-insensitive name comparator. */ const compareNames = (a: FileManagerInternalNode, b: FileManagerInternalNode): number => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }); @@ -518,7 +516,6 @@ export const buildFileManagerGridTemplate = (showSize: boolean, showModified: bo return cols.join(' '); }; -/** Recursively locate a node by its absolute path. Returns `undefined` when missing. */ export const findNodeByPath = ( nodes: readonly FileManagerInternalNode[], path: string, @@ -561,10 +558,8 @@ export const dedupeOverlappingPaths = (paths: Iterable): string[] => { return result; }; -/** Clamp `value` into the inclusive `[min, max]` range. */ export const clamp = (min: number, value: number, max: number): number => Math.max(min, Math.min(value, max)); -/** Translate `(isAllSelected, isSomeSelected)` into the tri-state value the Checkbox understands. */ export const getCheckboxState = (isAllSelected: boolean, isSomeSelected: boolean): 'indeterminate' | boolean => { if (isAllSelected) { return true; diff --git a/frontend/src/components/shared/file-manager/use-file-manager-selection.ts b/frontend/src/components/shared/file-manager/use-file-manager-selection.ts index d5c95cb3..acf6b448 100644 --- a/frontend/src/components/shared/file-manager/use-file-manager-selection.ts +++ b/frontend/src/components/shared/file-manager/use-file-manager-selection.ts @@ -78,8 +78,7 @@ export function useFileManagerSelection({ // expand/collapse and tree-shape changes. Without this, `onRowClick` would // be re-created on every expansion (its deps include `flatVisible`) and // invalidate the `onClick` prop on every memoized row — kicking the entire - // tree into a re-render on the most common user gesture. Same pattern is - // already used in `use-file-manager-dnd` for `selectionRef`. + // tree into a re-render on the most common user gesture. const flatVisibleRef = useRef(flatVisible); const allSelectablePathsRef = useRef(allSelectablePaths); const dirSubtreePathsRef = useRef(dirSubtreePaths); diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-content-integrity.test.ts b/frontend/src/components/shared/markdown-editor/markdown-editor-content-integrity.test.ts index 5ca90bd9..ee07e248 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-content-integrity.test.ts +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-content-integrity.test.ts @@ -42,9 +42,6 @@ const ATOMS = [ const WORDS = ['firewall', 'payload', 'exploit', 'nmap', 'recon', 'shell', 'token', 'vector']; -// Block contexts that keep inline content literal: paragraph, heading, bullet item, ordered item, -// blockquote, inline code. (Nested compositions — ordered>bullet>code, fence-in-fence — are exercised -// separately below.) const wrap = (context: number, text: string): string => { switch (context) { case 1: diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-heading-autoformat.test.ts b/frontend/src/components/shared/markdown-editor/markdown-editor-heading-autoformat.test.ts index e2ce2100..25964082 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-heading-autoformat.test.ts +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-heading-autoformat.test.ts @@ -44,9 +44,7 @@ const posOf = (editor: Editor, needle: string): number => { type EditResult = { heading: number; json: JSONContent; md: string; reloadHeading: number }; -// Fire ONE real transaction (appendTransaction runs only on transactions, never on construction) and snapshot -// the outcome: the live heading count + doc JSON, the serialized markdown, and how many headings that markdown -// reloads to (the round-trip check). +// appendTransaction runs only on transactions, never on construction — so fire ONE real transaction. const run = (content: JSONContent | string, edit: (editor: Editor) => void): EditResult => { const editor = newEditor(content); diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-image-src.test.ts b/frontend/src/components/shared/markdown-editor/markdown-editor-image-src.test.ts index 446e29c7..624082b1 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-image-src.test.ts +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-image-src.test.ts @@ -2,9 +2,7 @@ import { describe, expect, it } from 'vitest'; import { normalizeImageSrc, normalizeLinkUrl } from './markdown-editor-toolbar-url'; -// normalizeImageSrc makes a scheme-less src absolute (https://) and validates the protocol before it is saved: -// http(s) and base64 raster data: URLs pass through; data:image/svg+xml is rejected (SVG can carry script), as -// are data:text/html, application/*, javascript:, vbscript:, and malformed input. +// data:image/svg+xml is rejected because SVG can carry script — unlike raster data: URLs, which pass through. describe('normalizeImageSrc — prepend https to scheme-less src, validate protocol', () => { it.each([ ['example.com/a.png', 'https://example.com/a.png'], @@ -34,10 +32,8 @@ describe('normalizeImageSrc — prepend https to scheme-less src, validate proto }); }); -// A manually-typed link must be made absolute — a scheme-less input like "example.com" would otherwise persist as -// a relative href the browser resolves against the current origin. normalizeLinkUrl prepends https:// unless the -// input already carries an allowed scheme, validates the protocol with no base URL, and returns null for -// unsafe/malformed input. Already-schemed values pass through verbatim (case + query chars preserved). +// A scheme-less input like "example.com" would otherwise persist as a relative href the browser resolves against +// the current origin — normalizeLinkUrl prepends https:// to force it absolute. describe('normalizeLinkUrl — prepend https to scheme-less input, validate protocol', () => { it.each([ ['example.com', 'https://example.com'], diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-indented-fence.test.ts b/frontend/src/components/shared/markdown-editor/markdown-editor-indented-fence.test.ts index 17b0d943..2cf5026c 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-indented-fence.test.ts +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-indented-fence.test.ts @@ -17,8 +17,7 @@ const load = (md: string) => { // CommonMark allows a fenced code block's opening fence to be indented up to 3 spaces. Upstream // @tiptap/extension-code-block gated its parseMarkdown on `token.raw.startsWith('```')`, so an indented -// fence (whose raw begins with that whitespace) was dropped on load — and when a document mixed fence -// indents the mis-detection cascaded, silently deleting everything after the first dropped fence. +// fence (whose raw begins with that whitespace) was dropped on load. describe('indented fenced code blocks survive load', () => { it('keeps a 3-space-indented fence and its content', () => { const { json, out } = load('intro paragraph\n\n ```\ntail content KEEP\n```'); diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.test.ts b/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.test.ts index bdb65b0c..4a3bc3de 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.test.ts +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.test.ts @@ -24,7 +24,6 @@ describe('link handle popover key is stable while typing inside a link (LINK-REM editor.destroy(); expect(before && after).toBeTruthy(); - // The fix keys the popover on range.from alone: stable here, so the edit form is not remounted. expect(after!.from).toBe(before!.from); // range.to grew by the inserted char — the old `${from}-${to}` key would have remounted every keystroke. expect(after!.to).toBe(before!.to + 1); diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-styles.ts b/frontend/src/components/shared/markdown-editor/markdown-editor-styles.ts index d3c3748b..360fb917 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-styles.ts +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-styles.ts @@ -8,6 +8,5 @@ export const MARKDOWN_EDITOR_WRAPPER_CLASS = // stamps it on the contenteditable (markdown-editor.tsx) and with getEditorScrollParent below. export const EDITOR_CONTENT_CLASS = 'tiptap-content'; -// The scroll parent an editor overlay anchors to; falls back to window when the editor isn't inside a scroll box. export const getEditorScrollParent = (editorDom: Element): Element | Window => editorDom.closest(`.${EDITOR_CONTENT_CLASS}`) ?? window; diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-table-commands.ts b/frontend/src/components/shared/markdown-editor/markdown-editor-table-commands.ts index 9752bda2..b87c1941 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-table-commands.ts +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-table-commands.ts @@ -11,7 +11,6 @@ export const ALIGN_OPTIONS: { icon: LucideIcon; label: string; value: ColumnAlig { icon: AlignRight, label: 'Right', value: 'right' }, ]; -// Empties every cell in the caret's row or column, leaving the row/column structure intact (not a delete). export function clearLineContents(editor: Editor, axis: 'column' | 'row'): void { editor .chain() diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-table-handles.tsx b/frontend/src/components/shared/markdown-editor/markdown-editor-table-handles.tsx index 96c77ab7..01e864b5 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-table-handles.tsx +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-table-handles.tsx @@ -335,8 +335,7 @@ function useTableHandles(editor: Editor): TableHandlesController { // A grip captured before a scroll, resize, or edit points at a now-shifted position: scroll/resize move it // off the table visually, and a doc edit shifts every position after it, so a menu action would resolve // the stale cellPos against the current doc (wrong row/column, or an out-of-bounds RangeError in the - // header selector). Drop the target in every case — it reappears on the next hover. (link/image handles - // already dismiss on resize; TableHandles missed it and its grips detached from the table on window resize.) + // header selector). Drop the target in every case — it reappears on the next hover. const scrollParent = getEditorScrollParent(dom); const dropStaleTarget = () => { diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-table-pipes.test.ts b/frontend/src/components/shared/markdown-editor/markdown-editor-table-pipes.test.ts index 88bd7454..0e8419f4 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-table-pipes.test.ts +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-table-pipes.test.ts @@ -229,8 +229,6 @@ describe('CRLF line endings — tables still protected', () => { describe('TABLE_DELIMITER_LINE is linear (ReDoS guard)', () => { it('scans a crafted delimiter-looking line with a long trailing space run in linear time', () => { - // A crafted delimiter-looking line (dashes + a long trailing space run + non-matching tail) must scan in - // linear time, not O(n²). const evil = `x|y\n${'-'.repeat(50)}${' '.repeat(60000)}z\n`; const started = performance.now(); diff --git a/frontend/src/components/shared/route-error-boundary.test.tsx b/frontend/src/components/shared/route-error-boundary.test.tsx index afda64cb..d152536a 100644 --- a/frontend/src/components/shared/route-error-boundary.test.tsx +++ b/frontend/src/components/shared/route-error-boundary.test.tsx @@ -5,9 +5,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import RouteErrorBoundary from './route-error-boundary'; -// A loader that throws routes react-router to our `errorElement`, so -// `useRouteError` inside the boundary receives exactly this value — the same -// path a real chunk-load or render failure takes. const renderWithRouteError = (error: unknown) => { const router = createMemoryRouter( [ diff --git a/frontend/src/components/shared/route-error-boundary.tsx b/frontend/src/components/shared/route-error-boundary.tsx index fdc33dff..77dff7cb 100644 --- a/frontend/src/components/shared/route-error-boundary.tsx +++ b/frontend/src/components/shared/route-error-boundary.tsx @@ -19,9 +19,6 @@ function RouteErrorBoundary() { const isDesync = isDomDesyncError(error); useEffect(() => { - // A stale code-split chunk and a transient DOM desync (e.g. an extension or - // auto-translation mutated the tree) both self-heal with a debounced reload; - // any other error stays on the card for the user to retry. if (isChunk || isDesync) { reloadOnce(); } diff --git a/frontend/src/components/ui/data-table.test.tsx b/frontend/src/components/ui/data-table.test.tsx index ba12e737..08e98a7c 100644 --- a/frontend/src/components/ui/data-table.test.tsx +++ b/frontend/src/components/ui/data-table.test.tsx @@ -56,7 +56,6 @@ describe('DataTable — controlled filter projection', () => { { wrapper: Wrapper }, ); - // The body shows only "Bravo" — Alpha and Charlie are filtered out. expect(screen.getByText('Bravo')).toBeInTheDocument(); expect(screen.queryByText('Alpha')).not.toBeInTheDocument(); expect(screen.queryByText('Charlie')).not.toBeInTheDocument(); diff --git a/frontend/src/components/ui/data-table.tsx b/frontend/src/components/ui/data-table.tsx index 7d80ce28..043cc5c6 100644 --- a/frontend/src/components/ui/data-table.tsx +++ b/frontend/src/components/ui/data-table.tsx @@ -309,12 +309,6 @@ function DataTable({ const isRowInteractive = !!onRowClick || !!renderSubComponent; const { pathname } = useLocation(); - // Reuse the pathname we just read instead of letting the hook subscribe - // independently — react-router caches `useLocation` so the cost is - // negligible, but the explicit pass keeps the data flow obvious and - // makes it easy to migrate the table to a different storage scope (e.g. - // a workspace-prefixed path) without grepping for every subscription. - // // When `storageKey` is passed by the parent it wins — multi-table routes // (e.g. /settings/prompts) need distinct slots per instance, otherwise // their sorting / visibility / search-column narrowing alias and diff --git a/frontend/src/components/ui/sidebar.tsx b/frontend/src/components/ui/sidebar.tsx index b623b42e..69db9462 100644 --- a/frontend/src/components/ui/sidebar.tsx +++ b/frontend/src/components/ui/sidebar.tsx @@ -319,9 +319,6 @@ function SidebarProvider({ const { isMobile } = useBreakpoint(); const [openMobile, setOpenMobile] = React.useState(false); - // This is the internal state of the sidebar. - // We use openProp and setOpenProp for control from outside the component. - // First, try to read from cookie, fallback to defaultOpen const [_open, _setOpen] = React.useState(() => { const cookieValue = getSidebarState(); @@ -376,8 +373,6 @@ function SidebarProvider({ return () => window.removeEventListener('keydown', handleKeyDown); }, [toggleSidebar]); - // We add a state so that we can do data-state="expanded" or "collapsed". - // This makes it easier to style the sidebar with Tailwind classes. const state = open ? 'expanded' : 'collapsed'; const contextValue = React.useMemo( diff --git a/frontend/src/features/authentication/login-form.tsx b/frontend/src/features/authentication/login-form.tsx index 966076ab..e89541c2 100644 --- a/frontend/src/features/authentication/login-form.tsx +++ b/frontend/src/features/authentication/login-form.tsx @@ -57,7 +57,7 @@ const providerActions: AuthProviderAction[] = [ ]; interface LoginFormProps { - providers: string[]; // OAuth providers: ['google', 'github'] + providers: string[]; returnUrl?: string; } @@ -139,8 +139,6 @@ function LoginForm({ providers, returnUrl = routes.newFlow }: LoginFormProps) { } }; - // If password change is required, show password change form. - // Also check isAuthenticated() to ensure the user has a valid session. // If the session expired and user refreshed the page, the old authInfo may still // be in memory (race condition between clearAuth() and navigate()), but we must // NOT show the password change form because: diff --git a/frontend/src/features/flows/files/flow-files-attach-resources-dialog.tsx b/frontend/src/features/flows/files/flow-files-attach-resources-dialog.tsx index d0cc5899..66ee05e2 100644 --- a/frontend/src/features/flows/files/flow-files-attach-resources-dialog.tsx +++ b/frontend/src/features/flows/files/flow-files-attach-resources-dialog.tsx @@ -129,11 +129,8 @@ function FlowFilesAttachResourcesDialogBody({ }); /** - * Build the plan for the current selection. Earlier versions deduped - * descendants of any picked directory on the assumption that the backend - * would copy directory trees recursively — it does not. Until that - * becomes recursive on the backend, the user must multi-select a folder - * together with its children to attach the contents. + * The backend does not copy directory trees recursively, so the user must + * multi-select a folder together with its children to attach the contents. */ const buildPlan = useCallback((): AttachPlan | null => { if (selectedPaths.size === 0) { diff --git a/frontend/src/features/flows/files/flow-files-constants.ts b/frontend/src/features/flows/files/flow-files-constants.ts index 5a6ac2dc..fa452b07 100644 --- a/frontend/src/features/flows/files/flow-files-constants.ts +++ b/frontend/src/features/flows/files/flow-files-constants.ts @@ -29,10 +29,6 @@ export const RESOURCES_TARGET_DIRECTORY = '/work/resources'; export const CONTAINER_DEFAULT_PATH = '/work'; // ── Upload limits (mirror backend's `pkg/flowfiles/files.go`) ────────────── -// -// Symmetric with the resources library limits (`features/resources/resources-constants.ts`) -// today, but kept as a separate set so they can diverge from resources without -// touching the resources call sites. /** Mirrors `flowfiles.MaxUploadFileSize` (300 MB). */ export const FLOW_FILES_MAX_FILE_SIZE_MB = 300; diff --git a/frontend/src/features/flows/files/flow-files-promote-dialog.tsx b/frontend/src/features/flows/files/flow-files-promote-dialog.tsx index dca26820..018b9da4 100644 --- a/frontend/src/features/flows/files/flow-files-promote-dialog.tsx +++ b/frontend/src/features/flows/files/flow-files-promote-dialog.tsx @@ -164,8 +164,6 @@ function FlowFilesPromoteDialogForm({ files, flowId, onClose }: FlowFilesPromote */ const overwriteAction = useOverwrite({ execute: (plan, force) => promote(plan.sources, plan.destination, force), - // Local preflight against the resource library snapshot — flags the - // exact destinations already taken so the dialog can name them. findConflicts: (plan) => plan.targets.filter((t) => resourcePaths.has(t.destination)), onSuccess: onClose, // Race-fallback: backend doesn't return per-path conflict descriptors diff --git a/frontend/src/features/flows/files/flow-files-pull-dialog.tsx b/frontend/src/features/flows/files/flow-files-pull-dialog.tsx index 1002c8ac..5e38fe7a 100644 --- a/frontend/src/features/flows/files/flow-files-pull-dialog.tsx +++ b/frontend/src/features/flows/files/flow-files-pull-dialog.tsx @@ -185,12 +185,6 @@ function FlowFilesPullDialogForm({ cachedFiles, flowId, onClose, onSuccess }: Fl }, }); - /** - * Drive the canonical "Pull / Pull with overwrite / Replace all" workflow - * from the shared hook. The hook owns conflict-state, race-fallback and - * close-on-success — this dialog just provides the plan (paths) and the - * three pure helpers (find / execute / synthesize). - */ const overwriteAction = useOverwrite({ execute: (paths, force) => pull(paths, force), findConflicts: (paths) => findPullConflicts(paths, cachedFiles), @@ -285,8 +279,7 @@ function FlowFilesPullDialogForm({ cachedFiles, flowId, onClose, onSuccess }: Fl // the user is currently browsing. Non-empty selection wins and is mapped // back from the FileManager's name-keyed selection to absolute container // paths, then deduped so a folder + one of its descendants don't - // double-process. (Dedup is mostly defensive in this dialog because the - // listing is single-level, so descendants aren't visible.) + // double-process. const pullTargets = useMemo(() => { if (selectedPaths.size === 0) { return [currentPath]; diff --git a/frontend/src/features/flows/files/flow-files-utils.ts b/frontend/src/features/flows/files/flow-files-utils.ts index 17258de6..f53847dd 100644 --- a/frontend/src/features/flows/files/flow-files-utils.ts +++ b/frontend/src/features/flows/files/flow-files-utils.ts @@ -21,8 +21,7 @@ export type FlowFile = FlowFileFragmentFragment; /** * Wire shape of `models.FlowFile` (REST JSON, snake_case). The internal * `FlowFile` alias mirrors the GraphQL camelCase fragment for use in the - * FileManager UI. Current consumers of `FlowFilesResponse` only read - * `files.length` and `files[0].name`, so no conversion helper is needed yet. + * FileManager UI. */ export interface FlowFilesResponse { files: RestFlowFile[]; diff --git a/frontend/src/features/flows/files/flow-files.tsx b/frontend/src/features/flows/files/flow-files.tsx index 463f5afe..de5e805f 100644 --- a/frontend/src/features/flows/files/flow-files.tsx +++ b/frontend/src/features/flows/files/flow-files.tsx @@ -41,8 +41,6 @@ function FlowFiles() { const { flowId, flowStatus } = useFlow(); const [isPullDialogOpen, setIsPullDialogOpen] = useState(false); const [isAttachResourcesDialogOpen, setIsAttachResourcesDialogOpen] = useState(false); - // Array now: row-action click pushes a single-element array, the bulk bar - // pushes the deduped selection. Empty array / null closes the dialog. const [filesToPromote, setFilesToPromote] = useState(null); const { fileNodes, isInitialLoading, isLoading } = useFlowFilesData({ flowId }); @@ -75,9 +73,8 @@ function FlowFiles() { }, []); /** - * Bulk "copy paths" handler: join every selected file's path with `\n` so the - * user can paste a clean newline-separated list straight into the agent chat, - * a shell command, or a tool argument. Reports the count for clarity. + * Join the selected paths with `\n` so the result pastes as a clean + * newline-separated list into the agent chat, a shell command, or a tool argument. */ const handleBulkCopyPaths = useCallback(async (paths: string[]) => { if (paths.length === 0) { @@ -95,9 +92,8 @@ function FlowFiles() { toast.error('Failed to copy paths'); }, []); - // Single-file row download specialises the bulk URL builder via a 1-element - // array. `flowId` may be missing (no flow selected yet) — return '' so - // FileManager renders a noop link instead of crashing on `null`. + // `flowId` may be missing (no flow selected yet) — return '' so FileManager + // renders a noop link instead of crashing on `null`. const getRowDownloadHref = useCallback( (file: FileNode): string => buildFlowFilesDownloadHref(flowId, [file]) ?? '', [flowId], @@ -135,9 +131,6 @@ function FlowFiles() { [getRowDownloadHref, handleCopyPath, promoteAction, deletion.requestDelete], ); - // Bulk-action set: primary "Save as resources" (most common workflow on this - // page — promote interesting artifacts into the global library), copy-paths - // in overflow, destructive Delete on the right. const fileManagerBulkActions = useMemo( () => [ bulkDownloadAction(getBulkDownloadHref), diff --git a/frontend/src/features/flows/flow-form.tsx b/frontend/src/features/flows/flow-form.tsx index b3f59eb7..ad6bcb99 100644 --- a/frontend/src/features/flows/flow-form.tsx +++ b/frontend/src/features/flows/flow-form.tsx @@ -90,19 +90,14 @@ export function FlowForm({ const [providerSearch, setProviderSearch] = useState(''); const [templateSearch, setTemplateSearch] = useState(''); const [resourceSearch, setResourceSearch] = useState(''); - // Tracks which picker the combined dropdown is showing. Lifted to form - // state (instead of internal to the menu) so the tab choice survives - // re-renders triggered by `setTemplateSearch` / `setResourceSearch` - // inside the inner pickers. + // Lifted to form state so the tab choice survives re-renders triggered by + // `setTemplateSearch` / `setResourceSearch` inside the inner pickers. const [pickerTab, setPickerTab] = useState<'resources' | 'templates'>('templates'); const fileInputRef = useRef(null); - // Resources are rendered as a hierarchy: alphabetical sort by full path - // produces the right ordering for siblings at every depth (parents before - // their descendants, peers in alphabetical order). Each row's nesting level - // is then derived from the slash count and rendered as a left indent so the - // user can visually trace files into their parent directories. + // Alphabetical sort by full path yields hierarchical order: parents before + // their descendants, peers alphabetically at every depth. const sortedResources = useMemo( () => [...resources].sort((a, b) => a.path.localeCompare(b.path, undefined, { sensitivity: 'base' })), [resources], @@ -216,7 +211,6 @@ export function FlowForm({ const currentValues = getValues(); - // Update only fields that user hasn't manually changed and that differ from current values. // Arrays are compared shallowly so a new-but-identical `resourceIds` reference doesn't // trigger an unnecessary setValue (and the re-render it causes). Object.entries(defaultValues) @@ -328,10 +322,6 @@ export function FlowForm({ } }, [pendingTemplate, setValue]); - // Templates and resources share the same dropdown via tabs — both picker - // bodies are kept as render functions so each can be mounted directly - // inside its `` without duplicating the search-input + - // scrolled-list layout. const renderTemplatePickerInner = () => ( <> @@ -425,9 +415,8 @@ export function FlowForm({ const resourceId = String(resource.id); const isSelected = resourceIds.includes(resourceId); const Icon = resource.isDir ? Folder : FileText; - // Depth derived from the path's slash count; ignored while a - // search query is active so matches don't appear orphaned - // beneath hidden ancestors. + // Zeroed while a search query is active so matches don't appear + // orphaned beneath hidden ancestors. const depth = isResourceSearchActive ? 0 : resource.path.split('/').length - 1; return ( @@ -699,11 +688,9 @@ export function FlowForm({ - {/* Single upward-opening dropdown for both Templates and Resources - on every viewport. Sub-menus would get clipped on the narrowest - screens (~390px), and a unified UI keeps the form simpler than - branching on `isMobile`. The tab strip is rendered last so it - lands closest to the trigger button. */} + {/* Sub-menus would get clipped on the narrowest screens (~390px), so + Templates and Resources share one upward-opening dropdown. The tab + strip is rendered last so it lands closest to the trigger button. */} - {/* Mobile Tabs only */} {!isDesktop && ( )} - {/* Desktop and Mobile Tabs */}
- {/* Assistant Dropdown */} {flowId && ( )} - {/* Search Input */}
- {/* Thinking toggle button */} {shouldShowThinkingToggle && (
)} - {/* Thinking content */} {renderThinkingContent()} - {/* Main message content */} {message && ( )} - {/* Result details */} {result && (
` option lists. Co-located with the controls they feed because no -// other module needs them. const docTypeValues = [KnowledgeDocType.Answer, KnowledgeDocType.Guide, KnowledgeDocType.Code] as const; const guideTypeValues = Object.values(KnowledgeGuideType) as KnowledgeGuideTypeT[]; const answerTypeValues = Object.values(KnowledgeAnswerType) as KnowledgeAnswerTypeT[]; @@ -70,7 +68,6 @@ const LANGUAGES = [ interface KnowledgeContentFieldProps { control: Control; - /** When `true`, the editor stretches to fill its parent (desktop split view). */ fillParent?: boolean; hasLabel?: boolean; isSaving: boolean; @@ -120,13 +117,11 @@ export function KnowledgeContentField({ export function KnowledgeMetaFields({ control, isNew, isSaving }: KnowledgeMetaFieldsProps) { // Targeted subscription: only this component re-renders when docType changes, - // not the whole form. The full-form `useWatch` from the original code - // re-rendered on every keystroke in the markdown editor. + // not the whole form. A full-form `useWatch` re-renders on every editor keystroke. const docType = useWatch({ control, name: 'docType' }); - // `setValue` is used to clear subtype fields that no longer apply when the - // user switches docType. We do this synchronously inside `onValueChange` - // (rather than via `useEffect`) so that a fresh load of an existing - // document doesn't wipe its persisted subtype on first render. + // Clear no-longer-applicable subtype fields synchronously in `onValueChange`, + // not via `useEffect` — an effect keyed on docType would wipe an existing + // document's persisted subtype on first render. const { setValue } = useFormContext(); const handleDocTypeChange = (next: KnowledgeDocType, fieldOnChange: (value: KnowledgeDocType) => void) => { @@ -258,12 +253,7 @@ export function KnowledgeMetaFields({ control, isNew, isSaving }: KnowledgeMetaF render={({ field }) => ( Code language - {/* - * `Autocomplete` is a free-text input with a - * suggestion popover — the backend accepts any - * string here, so the dropdown is a UX hint - * rather than a closed enum. - */} + {/* Backend accepts any string — the dropdown is a UX hint, not a closed enum. */} item.question; const getHref = (item: Knowledge) => routes.knowledge(item.id); /** - * Detail-page navigation wired up for knowledge documents. Returns a - * `DetailNavigationController` for `` / - * `` / ``. The list page - * filters on `question` and the header shows the same, so `getLabel` - * doubles as the default searchable text. + * The list page filters on `question`, so `getLabel` doubles as the default + * searchable text (no explicit `getSearchableText` needed). */ export function useKnowledgeDetailNavigation(currentId: null | string | undefined) { const { knowledges } = useKnowledges(); diff --git a/frontend/src/features/resources/resources-constants.ts b/frontend/src/features/resources/resources-constants.ts index 56b1ad68..74d7fb1d 100644 --- a/frontend/src/features/resources/resources-constants.ts +++ b/frontend/src/features/resources/resources-constants.ts @@ -10,8 +10,7 @@ export const SEARCH_DEBOUNCE_MS = 300; // // The backend does not whitelist file extensions — any file is accepted as // long as the file name passes `validatePathComponent` (no `/ \ : * ? " < > |`, -// no control characters, ≤ 255 bytes). We re-state the size limits here so the -// client can fail fast instead of waiting for a 4xx response on huge uploads. +// no control characters, ≤ 255 bytes). /** Mirrors `resources.MaxUploadFileSize` (300 MB). */ export const MAX_FILE_SIZE_MB = 300; diff --git a/frontend/src/features/resources/resources-move-dialog.tsx b/frontend/src/features/resources/resources-move-dialog.tsx index f7a618c4..2f260b43 100644 --- a/frontend/src/features/resources/resources-move-dialog.tsx +++ b/frontend/src/features/resources/resources-move-dialog.tsx @@ -147,8 +147,6 @@ function ResourcesMoveDialogForm({ files, onClose }: ResourcesMoveDialogFormProp form.reset({ destination: defaultDestination }); }, [defaultDestination, form]); - // Lazy snapshot of every existing resource path. Recomputed only when the - // library changes; reused by `findConflicts` for the local preflight. const resourcePaths = useMemo(() => new Set(resources.map((resource) => resource.path)), [resources]); const sourcePaths = useMemo(() => new Set(files.map((file) => file.path)), [files]); diff --git a/frontend/src/features/resources/resources-utils.ts b/frontend/src/features/resources/resources-utils.ts index 7e0da780..c4510eed 100644 --- a/frontend/src/features/resources/resources-utils.ts +++ b/frontend/src/features/resources/resources-utils.ts @@ -5,7 +5,6 @@ import { baseUrl } from '@/models/api'; import { RESOURCES_DOWNLOAD_API_PATH } from './resources-constants'; -/** Convert a GraphQL `UserResource` into a `FileNode` consumed by the FileManager. */ export const toFileNode = (resource: UserResourceFragmentFragment): FileNode => ({ id: resource.id, isDir: resource.isDir, diff --git a/frontend/src/features/resources/use-resources-delete.ts b/frontend/src/features/resources/use-resources-delete.ts index ef3f0fd6..037e10c5 100644 --- a/frontend/src/features/resources/use-resources-delete.ts +++ b/frontend/src/features/resources/use-resources-delete.ts @@ -38,10 +38,6 @@ const deleteResourcesRequest = (paths: readonly string[]) => api.delete(`${RESOURCES_API_PATH}?${buildPathsQuery(paths)}`); /** - * Owns both the single-resource and bulk-delete flows. The component drives the - * confirmation dialog state through the returned `fileToDelete`/`requestDelete`/ - * `clearFileToDelete` triple, while the hook hides every API call and toast. - * * No imperative refetch is performed: the GraphQL `resourceDeleted` subscription * is wired into the Apollo cache and removes the deleted entries automatically. */ diff --git a/frontend/src/features/resources/use-resources-search.ts b/frontend/src/features/resources/use-resources-search.ts index ccd5dbaa..fc8c3318 100644 --- a/frontend/src/features/resources/use-resources-search.ts +++ b/frontend/src/features/resources/use-resources-search.ts @@ -17,10 +17,6 @@ interface UseResourcesSearchResult { * back to `/resources` starts clean. The debounce delay is preserved * (`SEARCH_DEBOUNCE_MS`) so the existing client-side tree filter still gets * the throttled value it expects. - * - * `clearPageOnFilterChange: false` because Resources has no `?page=` to - * reset — leaving the default would also work (deleting a non-existent - * param is a no-op), but the explicit setting documents intent. */ export function useResourcesSearch(): UseResourcesSearchResult { const { debouncedFilter, filter, resetFilter, setFilter } = useTableState({ diff --git a/frontend/src/features/templates/use-template-detail-navigation.ts b/frontend/src/features/templates/use-template-detail-navigation.ts index e8aa77c0..02e59999 100644 --- a/frontend/src/features/templates/use-template-detail-navigation.ts +++ b/frontend/src/features/templates/use-template-detail-navigation.ts @@ -7,12 +7,8 @@ const getId = (item: Template) => String(item.id); const getHref = (item: Template) => routes.template(item.id); /** - * Detail-page navigation wired up for templates. Returns a - * `DetailNavigationController