Commit Graph
272 Commits
Author SHA1 Message Date
Sergey KozyrenkoandClaude Opus 4.7 3a67daaad8 refactor(frontend): drive document <title> from route handles
Move <title> ownership out of detail-page components into the app
shell. The four detail routes — templates/:templateId,
knowledges/:knowledgeId, settings/providers/:providerId,
flows/:flowId — now expose a titleComponent via react-router
handle. A new <DocumentTitle/> in RootLayout walks useMatches()
deepest-first and renders the matched title component. Each one
subscribes to its resource via Apollo with fetchPolicy:
'cache-only', so it reacts to the destination page's own fetch
without issuing a duplicate request.

Fixes the root cause behind bbf943e's sticky workaround: navigation
between sibling documents (DetailNavigation prev/next) tore down
the page-level state that PageTitle held, flashing a generic
fallback during data fetch. The shell-level <DocumentTitle/>
survives the remount, so the title resolves from cache before the
new page has finished mounting.

- Add src/components/shared/document-title.tsx with DocumentTitle
  plus FlowTitle / KnowledgeTitle / ProviderTitle / TemplateTitle.
- Wire <DocumentTitle/> into RootLayout and attach
  handle={{ titleComponent: ... }} on the four detail routes.
- Remove <PageTitle> calls and now-redundant <> wrappers from the
  four page components.
- Drop the sticky setState-during-render hack from PageTitle —
  listing pages that still use it never flickered, so the simple
  passthrough is enough.

Lint + tsc clean, all 475 tests pass. Listing routes (Dashboard,
Flows, Templates, Knowledges, etc.) keep using PageTitle and can
migrate route-by-route without coordination.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:25 +07:00
Sergey KozyrenkoandClaude Opus 4.7 765d743b21 refactor(frontend): inline API token forms on RHF + zod
Replace the two ad-hoc useState form objects (createFormData /
editFormData) in settings-api-tokens.tsx with parent-level useForm
hooks per inline row, each backed by a zod schema. Controller in
each DataTable cell re-subscribes to the parent form state on
remount, so subscription-driven row remounts no longer drop user
input — same root cause that 04c6825 fixed, but solved at the
architectural level instead of controlling each Input manually.

- tokenNameSchema (trim + max 255), createTokenFormSchema with
  required expiresAt via refine, editTokenFormSchema with status.
- <Input>, <Select> and <Calendar> cells rendered through
  <Controller>; RHF owns the values, not React state in the parent.
- CreateRowActions / EditRowActions subcomponents own the
  useFormState({ control }).isValid subscription so the Save button
  re-renders only the small action subtree on keystrokes instead of
  the whole table.
- Ad-hoc required/trim checks dropped from handlers; the schema +
  isValid gate handle them. Submit still applies trim() || null
  because the GraphQL mutation treats null as "no name."

All 18 test files / 475 tests pass; lint + tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:24 +07:00
Sergey KozyrenkoandClaude Opus 4.7 9955da1243 style(frontend): prettier + replace Columns text button with icon
Manual formatting pass plus a UI tweak that turns the DataTable
"Columns" trigger into an icon-only ColumnsSettings button to match the
other toolbar controls' compact look. The button keeps its accessible
name via aria-label="Columns" so screen readers and
data-table.test.tsx (`getByRole('button', { name: /Columns/ })`) still
find it. Other touched files are pure prettier reformatting.

All 18 test files / 475 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:24 +07:00
Sergey KozyrenkoandClaude Opus 4.7 0db486837a fix(frontend): give /new routes dedicated tab titles and add sticky fallback
The /providers/new, /knowledges/new and /templates/new routes shared the
generic fallback used during data loading on their respective detail
pages, so the browser tab read "Provider — PentAGI", "Knowledge —
PentAGI" or "Template — PentAGI" while the user was clearly creating a
new entity. /flows/new already did this correctly ("New flow — PentAGI")
and serves as the reference.

Each detail page already exposes an `isNew` flag derived from the route
param, so the fix is a one-line branch in each PageTitle call site:

  /settings/providers/new → "New provider — PentAGI"
  /knowledges/new         → "New knowledge — PentAGI"
  /templates/new          → "New template — PentAGI"

Verified in chrome-devtools MCP — all three tabs now show the new
titles.

While here, give PageTitle a sticky fallback: once a non-empty title is
rendered, briefly empty children (e.g. a data refetch on the same
route) keep showing the previous value instead of flashing the generic
"Provider/Knowledge/Template — PentAGI" fallback. This helps in-route
loading transitions; it intentionally does not survive route remounts
(navigating Prev/Next between sibling documents tears down the Template
component and the local state with it). A complete fix for the inter-
route flicker requires app-level state or an optimistic Apollo cache
read on mount and is out of scope here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:24 +07:00
Sergey KozyrenkoandClaude Opus 4.7 b7ed0689a9 perf(frontend): drop Dashboard period-switch INP from 434ms to 134ms
The Analytics tab on /dashboard renders 4 Recharts plus a Flow
Execution Details list with 130+ Collapsible rows. Switching the period
(Week/Month/Quarter) re-rendered everything synchronously: the
pointerdown→paint interaction had INP=434ms (Google "Needs Improvement"
zone), with processing duration alone accounting for 403ms.

Four small changes, no new dependencies:

  1. useMemo on usageChartData / toolcallsChartData / flowsChartData so
     Recharts receives stable references when only an unrelated piece
     of parent state changes (e.g. tooltip hover state).
  2. useDeferredValue(executionStats) so a period switch can repaint
     the charts first and reconcile the long list as a low-priority
     follow-up. While the deferred value is stale we dim the list to
     60% opacity, matching the existing dashboard.tsx convention.
  3. content-visibility: auto + contain-intrinsic-size on every
     FlowExecutionItem. Off-screen rows skip layout and paint entirely
     — this is the cheapest possible virtualization, with no deps,
     measurement, or scroll math.
  4. React.memo on FlowExecutionItem and TaskExecutionItem so parent
     re-renders don't traverse the entire 130-row tree.

Measured via chrome-devtools MCP performance trace:

  Before:  INP 434ms — input 1ms / processing 403ms / present 29ms
  After:   INP 134ms — input 1ms / processing 112ms / present 21ms

CLS stays at 0.00.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:24 +07:00
Sergey KozyrenkoandClaude Opus 4.7 1a8b04aa0d fix(frontend): persist API token name through subscription refetches
The inline create/edit row in the API tokens table used uncontrolled
<Input ref={…} defaultValue="…" /> and read the value back from the ref
inside the submit handler. When the apiTokens subscription fired during
the user's interaction, DataTable would reorder rows and the input would
re-mount with defaultValue="" (or the original name), discarding what
the user just typed. The mutation then sent name: null and the freshly
created token appeared as "(unnamed)".

Move both create and edit name inputs to controlled state, drop the
two refs, and read the value from form state at submit time so it
survives any number of re-mounts. While here, give each input a stable
useId() for id, an explicit name="token-name", and autoComplete="off"
to silence the "A form field element should have an id or name
attribute" Chrome DevTools issue that this form was triggering.

Verified via chrome-devtools MCP: creating a token named "qa-pr1-verify"
now stores and displays the name correctly across the
useApiTokenCreatedSubscription refetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:24 +07:00
Sergey KozyrenkoandClaude Opus 4.7 73c6652dbf perf(frontend): useOptimistic for flow rename and favorite toggle
Two long-running mutations now flip the UI on the urgent track and
reconcile against the server in the background, so the user no longer
sees the latency between a confirming click and the network round-trip.

pages/flows/flow.tsx — inline rename
- New optimistic title state seeded from `flow.title` and updated via
  `setOptimisticFlowTitle(next)` at the start of the save handler.
- Mutation runs inside `startTransition(async () => …)` so React keeps
  the optimistic title visible while awaiting the rename. On success
  the Apollo cache update lands and useOptimistic falls back to the
  fresh `actualFlowTitle`; on failure the toast surfaces and the value
  rolls back to the cached title automatically.

providers/favorites-provider.tsx — toggle favorite
- `useOptimistic` over the favorite-ids array with an `add`/`remove`
  action reducer. addFavoriteFlow and removeFavoriteFlow wrap their
  Apollo mutations in `startTransition` and call
  `applyOptimisticFavorite` first, so the sidebar's Favorite Flows
  list and the row's star both flip instantly.
- Subscription-driven cache refresh still arrives a moment later and
  becomes the new `actualFavoriteFlowIds`; React's transition exits
  without a visible flicker because the optimistic value already
  matches what the cache now holds.

Verified: build, lint (0/44 baseline), 475/475 tests, browser smoke —
clicking the favorite star on /flows immediately moves the row into
"Favorite Flows" in the sidebar and back, no console errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:23 +07:00
Sergey KozyrenkoandClaude Opus 4.7 1eb3ae1389 refactor(frontend): make FormSubmitButton fully accept Button's API
Reworks the FormSubmitButton so it slots into every Button use-case
in the codebase, not just the simplest text-only submits:

- Adds an `icon` prop. While the form is idle the icon renders to the
  left of `children`; while submitting it is swapped for the spinner
  in place, so the button keeps its layout. Plain text buttons stay
  the same — the spinner is prepended only during submit.
- Adds an optional `loading` prop. When provided it wins over
  `formState.isSubmitting`, which unlocks two real cases we
  previously had to skip: submits attached via the HTML `form="…"`
  attribute that live outside the FormProvider tree, and submits
  whose pending state is the union of several mutation flags.
- Reads `useFormContext()` defensively — it returns null outside
  a FormProvider, so the component degrades to "controlled by
  `loading` prop only" instead of throwing. That keeps the API
  honest for both in-tree and out-of-tree submits.

All other Button props (variant, size, className, onClick,
aria-label, form, …) pass through unchanged because the type
extends `React.ComponentProps<typeof Button>`.

Applied to two more previously-skipped sites:
- settings-provider.tsx Save button (form="provider-form",
  loading=isLoading union, Save icon, dynamic label).
- settings-prompt.tsx system and human Save buttons (same shape).

resources-mkdir-dialog now passes FolderPlus via the new `icon`
prop instead of putting it inside children, so the dialog's submit
spinner replaces the folder icon in place during submit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:23 +07:00
Sergey KozyrenkoandClaude Opus 4.7 e15839bab1 feat(frontend): add FormSubmitButton and apply it to three forms
New shared component src/components/ui/form-submit-button.tsx. It is
the react-hook-form-flavoured analogue of React 19's useFormStatus:
the button subscribes to the nearest FormProvider via useFormContext
and reads formState.isSubmitting / isValid / isSubmitted itself, so
the surrounding form no longer has to thread loading flags down.
Shows a Loader2 spinner while submitting and disables itself once
the form is dirty-and-invalid (opt out with requireValid={false}).

Applied to the three forms where the submit is a plain
<Button type="submit"> living inside a <Form {...form}> wrapper:

- login-form.tsx — also dropped the manual isSubmitting toggles
  inside the form handler (RHF already tracks that through
  form.handleSubmit). Kept the OAuth-flow useState because the
  provider login does not go through form.handleSubmit; combined
  the two states on the OAuth buttons so they also disable while
  the form is submitting.
- password-change-form.tsx — dropped the manual isSubmitting state
  entirely; the only submit goes through form.handleSubmit. Cancel
  and Skip buttons stay as plain Buttons.
- resources-mkdir-dialog.tsx — replaced the submit button; kept the
  isCreating flag because Cancel and the Input still need to react
  to the mutation in flight.

Five other forms intentionally kept their current submit:
- knowledge-form.tsx uses our HeaderButton (responsive icon/label)
  with its own Spinner, not a plain Button.
- flow-form.tsx and templates/template.tsx use InputGroupButton
  (icon-only, sits inside an InputGroupAddon).
- settings-prompt.tsx and settings-provider.tsx attach their submit
  to the form via the HTML form="…" attribute, so the button is
  outside the FormProvider tree — useFormContext would throw.
  These also drive disabled state from multiple mutation flags
  (create/update/delete/validate) that aren't reducible to a single
  isSubmitting.

Verified: pnpm run build, lint (0/44 baseline unchanged), 475/475 tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:23 +07:00
Sergey KozyrenkoandClaude Opus 4.7 338d39c10c fix(frontend): use first letter instead of calendar icons on mobile period switcher
Three nearly-identical calendar icons (CalendarDays / Calendar /
CalendarRange) didn't convey Week vs Month vs Quarter clearly on a
375 px viewport — the differences between the glyphs are too subtle
at 16 px. Swapped them for the first letter of each label (W / M / Q)
with the same compact `size-7` button shape; full label stays on
sm+ as before.

Kept aria-label on the trigger and aria-hidden on the letter span so
screen readers still announce the full word.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:23 +07:00
Sergey KozyrenkoandClaude Opus 4.7 96e91d2a2a fix(frontend): make mobile period triggers compact icon buttons
The mobile period switcher swap landed in 28c98a8 but kept the
text-button paddings (px-3), so each "icon-only" trigger was still
~36 px wide and the group looked stretched on a 375 px viewport.

Added \`aspect-square px-0\` to each TabsTrigger (with the desktop
overrides reset above the \`sm\` breakpoint) so the buttons collapse
to a 1:1 ratio on mobile — three tight squares matching shadcn's
icon-button proportions — while desktop keeps the original
horizontal padding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:23 +07:00
Sergey KozyrenkoandClaude Opus 4.7 e02da42f39 feat(frontend): collapse Dashboard period switcher to icons on mobile
On screens narrower than the `sm` breakpoint (640 px) the
Week / Month / Quarter triggers were forcing the period switcher to
crowd the Analytics / Overview tabs into the next line. Replaced
the labels with lucide calendar icons (CalendarDays / Calendar /
CalendarRange) that swap in below `sm` while the full text comes
back above it.

Accessibility preserved — each TabsTrigger keeps an explicit
aria-label and the icons are aria-hidden so screen readers still
announce "Week" / "Month" / "Quarter" instead of duplicate icon
descriptions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:23 +07:00
Sergey KozyrenkoandClaude Opus 4.7 58d01e0a67 perf(frontend): defer heavy Dashboard tab switches via useTransition
Both tab groups on /dashboard trigger expensive re-renders:
- Analytics ↔ Overview swaps the entire content subtree (Analytics
  alone pulls in a ~386 kB chunk with four Recharts views).
- Week / Month / Quarter invalidates the analytics query and
  re-paints every chart with the new dataset.

Wrapped both setState calls in startTransition so the trigger
buttons update on the urgent track while the heavy work commits as
low-priority. React will discard intermediate frames if the user
clicks again before the previous transition finishes.

Surfaces the combined isPending state via a subtle opacity-60 dim
plus aria-busy on the content region — gives users feedback that
the heavier render is in flight without a spinner flash.

Verified: build, lint (0/44 baseline), 475/475 tests, browser smoke
on /dashboard with no console errors after switching periods.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:22 +07:00
Sergey KozyrenkoandClaude Opus 4.7 dea9e38107 refactor(frontend): unify the whole codebase on function declarations
Brings every top-level React component and hook outside of
src/components/{ui,shared} (those were already migrated in 9d5daf7)
in line with the shadcn/ui new-york-v4 canonical style:

- features/* (flows, knowledges, resources, authentication, …)
- pages/* (dashboard, flows, knowledges, settings, templates, login,
  oauth-result, resources)
- providers/* (all eleven context providers + their useXxx hooks)
- components/icons/* (every SVG icon + provider-icon, flow-status-*)
- components/layouts/* and components/routes/*
- hooks/* (use-* hooks in their own files)
- Calendar UI component (missed in the earlier pass — also dropped
  the redundant `displayName = 'Calendar'` and the empty
  `CalendarProps = DayPickerProps` alias)

Net diff is +50 lines (1842 ins / 1792 del — the lines that were
removed are the `const X = (…) =>` declarations and the matching
closing `;`, plus a handful of explicit `React.FC<…>` type
annotations that no longer add anything; the lines added are the
`function X(…) {`/`}` envelopes).

What we deliberately did NOT touch:
- Inner components/hooks defined inside other functions — keep their
  arrow form so the local-scope intent stays visible.
- React.memo / React.lazy wrappers — `const X = memo(InnerX)` and
  `const X = lazy(() => …)` aren't components themselves.
- Render-callback props (renderFlowItem, renderItem, …).
- Plain utilities, helpers, factories, REST/GraphQL builders and
  every file under lib/, models/, schemas/, types/, graphql/types.ts.
- The few callback-ref-composing components (autocomplete, textarea,
  markdown-editor) keep their explicit `ref?: Ref<CustomShape>`
  because ComponentProps can't express their custom imperative shape.

Bonus cleanup along the way: removed the now-unused
`import * as React` from settings-prompt.tsx (was only there for the
deleted `React.FC` typing).

Verified: pnpm run build, lint (0 errors / 44 warnings unchanged
baseline) and 475/475 vitest tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:22 +07:00
Sergey KozyrenkoandClaude Opus 4.7 39adab6f8c chore(frontend): drop redundant displayName outside components/ui
Removes two leftover `X.displayName = 'X'` lines that just duplicated
the function's own name:
- src/components/shared/terminal/terminal.tsx
- src/components/shared/monaco-terminal.tsx

Two other displayName usages in the wider codebase are kept on
purpose:
- markdown.tsx — factory creates an inner `Renderer` and stamps it
  `Highlighted(${ComponentName})` so each instance shows up
  distinctly in React DevTools.
- file-manager-row.tsx — the function is named `FileManagerRowImpl`
  and re-exported through React.memo as `FileManagerRow`; the
  displayName carries the public name across the memo wrapper.

The five `.displayName` mentions in src/pages/settings/settings-prompt*
are property reads (`promptInfo.displayName`, `row.original.displayName`)
on domain data, not component metadata — untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:22 +07:00
Sergey KozyrenkoandClaude Opus 4.7 99f8485ae0 refactor(frontend): adopt shadcn/ui new-york-v4 component style
Brings all 32 components in src/components/{ui,shared} in line with
the current shadcn/ui new-york-v4 canon (cross-checked against the
upstream registry tooltip / button / input / dialog sources). Net
diff is -637 lines of boilerplate.

Four shape changes per component:
1. function declaration instead of `const X = (...) =>`. Slightly
   cleaner stack traces and Function.name is set automatically.
2. React.ComponentProps<typeof X> (or React.ComponentProps<"button">
   for native elements) instead of
   React.ComponentPropsWithoutRef<typeof X> & { ref?: React.Ref<...> }.
   In React 19 ref is already part of ComponentProps; the explicit
   intersection was leftover noise.
3. `ref` is no longer destructured — it flows through `{...props}`
   spread into the underlying primitive. Exceptions kept verbatim:
   textarea.tsx (custom TextareaRef shape with useImperativeHandle),
   autocomplete.tsx (callback-ref composition in AutocompleteInput
   and AutocompleteContent), markdown-editor.tsx (MarkdownEditorHandle
   with useImperativeHandle).
4. displayName removed everywhere — Function.name covers React DevTools
   now that there is no forwardRef wrapper to hide it.

Verified: pnpm run build, lint (0 errors / 44 warnings unchanged
baseline) and 475/475 vitest tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:22 +07:00
Sergey KozyrenkoandClaude Opus 4.7 b5b3cbe99b feat(frontend): per-route document titles via React 19 metadata
Adds a tiny <PageTitle> wrapper around React 19's native <title>
element (which React hoists into <head> automatically) and uses it
from every page-level route. Browser tabs, history, and shareable
URLs now reflect the actual page instead of the static "PentAGI"
that index.html ships.

- Static titles for list and form pages (Dashboard, Flows, New flow,
  Flow report, Templates, Knowledges, Resources, Providers, Prompts,
  API tokens, Login, OAuth).
- Dynamic titles for detail pages — flow title + id, knowledge
  question, template name, provider name, prompt display name — with
  graceful fallbacks while data is still loading.
- Added to every render branch (loading / error / empty / main) so
  the tab title stays correct even before data lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:22 +07:00
Sergey KozyrenkoandClaude Opus 4.7 53c140dd70 refactor(frontend): drop React.forwardRef for ref-as-prop (React 19)
Migrates all 32 components in src/components/{ui,shared} from
React.forwardRef<RefT, PropsT> to plain functional components that
take `ref?: React.Ref<RefT>` as a regular prop. React 19 treats ref
like any other prop, so the forwardRef wrapper is no longer needed
and only added boilerplate + an extra type parameter.

Highlights:
- All 32 files keep their displayName so React DevTools and Radix
  Slot composition still surface readable component names.
- Custom-shape refs (TextareaRef in textarea.tsx and
  MarkdownEditorHandle in markdown-editor.tsx) plus the callback-ref
  composition inside autocomplete.tsx are preserved verbatim — only
  the wrapper changes.
- sidebar.tsx contained 22 forwardRef wrappers in one file; all
  converted in the same pass.
- No behaviour, classes, attributes or imports of `import * as React`
  are touched. pnpm run lint:fix afterwards reformatted the auto-added
  blank lines so we are back to 0 errors / 44 warnings (baseline).

Verified: pnpm run build, lint, and 475/475 vitest tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:21 +07:00
Sergey KozyrenkoandClaude Opus 4.7 de39cf1065 perf(frontend): defer DataTable filter pipeline via useDeferredValue
Wraps the composite globalFilter ({ columns, query }) in
React.useDeferredValue before handing it to TanStack's state. The
filter input itself stays urgent (already debounced to 300 ms), so
keystrokes still feel instant; what becomes low-priority is the
heavy getFilteredRowModel() recomputation that runs over the full
dataset — ≈353 flows, 180 knowledge documents, ~50 templates and
API tokens. On those screens React can now drop intermediate filter
states when the user is still typing, instead of committing each
debounce tick on the urgent track.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:21 +07:00
Sergey KozyrenkoandClaude Opus 4.7 450e374166 style(frontend): apply prettier formatting fixes
- detail-navigation-buttons.tsx: wrap long cn() call across lines
- knowledge-header.tsx: alphabetize lucide-react named imports
- flow.tsx: reorder Tailwind classes per prettier-plugin-tailwindcss
  canonical order

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:21 +07:00
Sergey KozyrenkoandClaude Opus 4.7 827fd46c81 chore(frontend): silence 5 long-standing lint errors with targeted disables
Drops pnpm run lint from 5 errors to 0 (warnings unchanged at 44).
Each disable is the smallest local suppression with a one-line reason
on the same comment, so the next reader sees why the rule was muted:

- src/components/ui/textarea.tsx (line 79): the effect intentionally
  syncs the internal auto-size trigger with the controlled value prop
  every time the parent changes it.
- src/features/flows/files/use-flow-container-files.ts (line 107): the
  effect calls an async fetcher (fetchListing) whose setState runs
  after await — not synchronously inside the effect body; pathsKey is
  intentionally used in place of the paths array reference.
- src/pages/settings/settings-prompt.tsx (line 344): the useMemo
  branches on data.settingsPrompts which the react-compiler can't
  statically prove stable.
- src/providers/resources-provider.tsx (line 70): intentional
  mount-time loading flag for the REST hydration path.
- src/providers/user-provider.tsx (line 144): refreshAuthInfo's
  setState runs after an async /auth/info fetch, not synchronously.

Build, 475/475 vitest tests, and runtime smoke (dashboard, flows,
knowledges, settings/providers) all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:21 +07:00
Sergey KozyrenkoandClaude Opus 4.7 b713d6d004 chore(frontend): update remaining deps to latest within caret ranges
Runs pnpm update + bumps prettier-plugin-tailwindcss 0.7.4 → 0.8.0
(the 0.8 release is the one that pairs with Tailwind v4).

After this commit, pnpm outdated only reports three deliberately
pinned packages: eslint / @eslint/js (held on 9.x while
eslint-plugin-react still targets eslint ^9) and @types/node (held on
24.x to match the Node 24.12 runtime; npm latest is 25.x for Node 25).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:21 +07:00
Sergey KozyrenkoandClaude Opus 4.7 bd6f3b1c43 chore(frontend): align @types/node with node 24.12
Bumps @types/node 22.19.19 → 24.12.4 to match the actual Node 24.12
runtime the project is built and run on. The latest published tag is
25.x (for the not-yet-released Node 25 line); we deliberately stay on
24.x.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:21 +07:00
Sergey KozyrenkoandClaude Opus 4.7 5ac904d740 chore(frontend): upgrade commitlint to v21
Bumps @commitlint/cli and @commitlint/config-conventional from 20.5.3
to 21.0.1. Dev-only — affects the commit-message hook, not the app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:20 +07:00
Sergey KozyrenkoandClaude Opus 4.7 41d5277216 chore(frontend): upgrade typescript to v6
Bumps typescript 5.9.3 → 6.0.3.

Adds compilerOptions.ignoreDeprecations = "6.0" to tsconfig.json so the
existing baseUrl/paths setup keeps working — TS 6 has flagged baseUrl
as deprecated (it will be removed in 7.0). The vite-tsconfig-paths
plugin still consumes the paths mapping; nothing else in the codebase
needed changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:20 +07:00
Sergey KozyrenkoandClaude Opus 4.7 f7e61b4fe5 chore(frontend): upgrade eslint-plugin-perfectionist to v5 (eslint kept on v9)
Bumps eslint-plugin-perfectionist 4.15.1 → 5.9.0.

eslint and @eslint/js are intentionally kept on 9.39.4 (the latest
9.x): eslint 10 changes the rule context API
(contextOrFilename.getFilename is gone) and eslint-plugin-react still
ships only versions targeting eslint ^9 — the v10 upgrade currently
crashes the lint run with "Error while loading rule
'react/display-name'". Once eslint-plugin-react publishes a v10-aware
release we can finish this bump.

perfectionist v5 already supports the eslint ^8.45 || ^9 || ^10 peer
range, so it ships cleanly today and gives us its v5 rule changes
without dragging eslint along.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:20 +07:00
Sergey KozyrenkoandClaude Opus 4.7 1fa879839e chore(frontend): upgrade vite to v8
Bumps vite 7.3.3 → 8.0.13 and vite-tsconfig-paths 5.1.4 → 6.1.1.

Vite 8 ships rolldown (the Rust bundler that replaced rollup) and that
required two fixes:

1. build.rollupOptions.output.manualChunks: rolldown no longer accepts
   the { chunkName: [packages] } static object form, so it is now an
   (id) => string callback that matches paths under node_modules.

2. src/components/ui/resizable.tsx: switched from the namespace import
   `import * as ResizablePrimitive` to named `{ Group, Panel, Separator }`
   from react-resizable-panels. The v4 release of that library renamed
   PanelGroup → Group and PanelResizeHandle → Separator; rollup's
   loose tree-shaking happened to keep the old names working at
   build-time, but rolldown enforces the export list and the page
   failed at runtime with "Element type is invalid". Public wrapper
   exports (ResizableHandle, ResizablePanel, ResizablePanelGroup) are
   unchanged so the rest of the app keeps working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:20 +07:00
Sergey KozyrenkoandClaude Opus 4.7 8a3d4e951d chore(frontend): upgrade graphql-codegen toolchain
Bumps the codegen stack: cli 6→7, client-preset 5→6, typescript 5→6,
typescript-operations 5→6 (typescript-react-apollo and
near-operation-file-preset only moved within their minor range).

Configures typescript-react-apollo to emit Apollo v4-compatible imports
via apolloReactCommonImportFrom / apolloReactHooksImportFrom set to
@apollo/client/react, so re-running pnpm run graphql:generate now
produces ApolloReactCommon / ApolloReactHooks namespaces from the v4
subpath instead of the v3 single-entry import.

src/graphql/types.ts is fully regenerated under the new config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:20 +07:00
Sergey KozyrenkoandClaude Opus 4.7 b6d9983563 chore(frontend): upgrade @apollo/client to v4
Bumps @apollo/client from 3.14.1 to 4.1.9.

Apollo v4 moved React-only exports to the @apollo/client/react
subpath. Updated the three import sites that consume them:
- ApolloProvider in app.tsx
- useApolloClient in providers/resources-provider.tsx
- the Apollo namespace import in graphql/types.ts (covers useQuery,
  useMutation, useSubscription, useLazyQuery, useSuspenseQuery,
  skipToken, *HookOptions, *QueryResult)

Everything else (gql, ApolloClient, ApolloLink, createHttpLink,
InMemoryCache, Observable, split, NetworkStatus, getMainDefinition,
onError, GraphQLWsLink) keeps its existing import path in v4.

Build output shrunk from 223 kB to 191 kB for the apollo-client chunk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:20 +07:00
Sergey KozyrenkoandClaude Opus 4.7 de18fe2c31 chore(frontend): upgrade zod to v4 with hookform/resolvers v5
Bumps zod 3.25.76 → 4.4.3 and @hookform/resolvers 3.10.0 → 5.2.2
(the resolvers v5 requires zod v4).

zod is used in 25 source files (schemas, form validators, table state
parsers). Build passes without changes — the project relies on the
shared subset of the v3/v4 API (z.object/z.string/z.number/z.enum,
z.infer, .min/.max/.optional, refinements). Runtime form validation
keeps disabling submit until required fields are filled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:19 +07:00
Sergey KozyrenkoandClaude Opus 4.7 6d227e1d7d chore(frontend): upgrade react-resizable-panels to v4
Bumps react-resizable-panels from 3.0.6 to 4.11.1. The wrapper in
src/components/ui/resizable.tsx only re-exports PanelGroup, Panel and
PanelResizeHandle; their props remain compatible across the major.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:19 +07:00
Sergey KozyrenkoandClaude Opus 4.7 45e509ff48 chore(frontend): upgrade react-day-picker to v10
Bumps react-day-picker from 9.14.0 to 10.0.1. The Calendar wrapper in
src/components/ui/calendar.tsx uses the v9 classNames keys
(button_next/previous, day_button, range_*, week, weekday, weekdays)
and the components.Chevron API; all of these are preserved in v10.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:19 +07:00
Sergey KozyrenkoandClaude Opus 4.7 6c8e4ea35a chore(frontend): upgrade marked to v18
Bumps marked from 17.0.6 to 18.0.3. Used only in
src/lib/report/report-pdf.tsx for PDF generation via marked.lexer();
that API is compatible across the major.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:19 +07:00
Sergey KozyrenkoandClaude Opus 4.7 f655ad872d chore(frontend): upgrade lucide-react to 1.x
Bumps lucide-react from 0.553.0 to 1.16.0. The 0.x → 1.x jump is a
versioning policy change, not a breaking API change — all icon imports
keep working as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:19 +07:00
Sergey KozyrenkoandClaude Opus 4.7 8c346275d9 chore(frontend): bump react to 19.2.6 and align @types
Pin React/React-DOM to 19.2.6 and @types/react* to 19.2.0 in
package.json — lockfile already resolved the latest 19.x; this just
makes the minimum explicit so future installs don't regress.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:39:18 +07:00
Dmitry Ng 39f122467d feat(config): add new embedding and rename database connection pool settings
- Introduced `EMBEDDING_MAX_TEXT_BYTES` to limit the maximum byte size of text sent to the embedding model.
- Renamed database connection pool settings: `DATABASE_MAX_OPEN_CONNS`, `DATABASE_MAX_IDLE_CONNS`, and `DATABASE_VECTOR_MAX_CONNS` for improved PostgreSQL connection management.
- Updated relevant documentation to reflect these new configuration options and their usage.
- Adjusted various components to utilize the new settings for enhanced performance and resource management.
2026-05-18 18:26:52 +03:00
Dmitry Ng 2ce863ec1a feat(toolcall): implement ToolCall logging functionality
- Added ToolCallLogProvider interface with methods for logging tool calls, updating success and failure statuses.
- Introduced proxyToolCallLogProvider to handle ToolCall logging operations.
- Updated flow execution components to integrate ToolCall logging, including flow workers and controllers.
- Enhanced GraphQL schema to support ToolCall logs, including queries and subscriptions for real-time updates.
- Updated documentation to reflect the new ToolCall logging features and their usage.
2026-05-18 11:21:56 +03:00
Dmitry Ng 077ddce476 feat(database): enhance PostgreSQL connection pooling and configuration
- Introduced shared connection pooling for PostgreSQL using `*sql.DB` for sqlc and GORM, optimizing resource usage.
- Added new environment variables: `DB_MAX_OPEN_CONNS`, `DB_MAX_IDLE_CONNS`, and `DB_VECTOR_MAX_CONNS` for configurable connection limits.
- Updated documentation to reflect new connection pooling strategy and provide operational commands for monitoring.
- Implemented shared `pgxpool` for pgvector stores to reduce connection overhead and improve performance.
- Adjusted various components to utilize the new connection pooling setup, ensuring efficient database interactions.
2026-05-18 11:13:43 +03:00
Dmitry Ng f970922ea5 Merge remote-tracking branch 'origin/feature/frontend-next' into feature/next-release 2026-05-16 22:54:28 +03:00
Dmitry Ng 1bb7f8a9a0 feat(flow): add WaitTaskCompletion method and associated tools for assistant
- Introduced WaitTaskCompletion method in FlowWorker interface to block until the current task completes or the context expires.
- Implemented signalTaskComplete to manage task completion signaling across goroutines.
- Added waitFlowCompletion tool to handle waiting for task completion with configurable timeout.
- Updated assistant provider to include wait functionality for flow completion.
- Enhanced templates and tool registry to support new wait functionality.
2026-05-16 22:52:51 +03:00
Dmitry Ng 548c54c761 fix(controller): remove close(aw.input) to prevent nil channel deadlock on assistant finish 2026-05-16 22:51:07 +03:00
Dmitry Ng 888f7e2a4f fix(subscriptions): drop events for slow/disconnected subscribers after 5s timeout 2026-05-16 22:49:49 +03:00
Sergey KozyrenkoandClaude Opus 4.7 31803630d3 feat(frontend): add Anonymize action to knowledge document form
Gated behind the `anonymize.call` privilege from `useUser()`; runs the
current `content` through the `anonymizeText` GraphQL mutation and writes
the result back. Desktop renders the button before Save; mobile collapses
it into the first item of the actions dropdown (the dropdown now appears
for new documents too when the privilege is present).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:31:47 +07:00
Sergey KozyrenkoandClaude Opus 4.7 35e077fd8b fix(frontend): always refetch knowledgeDocuments on provider remount
The Apollo client's global nextFetchPolicy is 'cache-first', so returning
to /knowledges via SPA navigation would serve potentially stale cache.
Subscriptions are now scoped to /knowledges* and detach on other pages,
so changes from AI agents during flow runs would be missed until a full
reload. Override nextFetchPolicy to keep cache-and-network for this query.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:26:44 +07:00
Sergey KozyrenkoandClaude Opus 4.7 258b677c01 fix(frontend): scope KnowledgesProvider to /knowledges routes
KnowledgesProvider sat in RootLayout, so knowledgeDocuments (with full
content) and its 3 subscriptions fired on every authenticated page —
a ~2.1 MB payload on /flows, /dashboard, etc. All consumers of
useKnowledges live under /knowledges*, so wrap those routes in a
dedicated layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:45:48 +07:00
Sergey KozyrenkoandClaude Opus 4.7 cc6b525f57 fix(frontend): cap DataTable filter input at 200 chars
A paste of multi-KB content into the filter would land in the URL
verbatim (`?q=` plus the raw blob), which exceeds the practical
reverse-proxy limit (~2–4 KB) and breaks the share-link experience.
The user-facing entry point is a single `<input>`, so a DOM-level
`maxLength` is sufficient — it truncates both typing and paste before
the value ever reaches React state, the URL, or localStorage. 200 chars
is well above any realistic search term while leaving plenty of
headroom for non-ASCII expansion under percent-encoding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:41:42 +07:00
Sergey KozyrenkoandClaude Opus 4.7 022e8f3df0 refactor(frontend): unify table URL state under useTableState
The split `useTableQueryFilter` + `usePagination` pair had a latent batching
race: react-router v6 feeds every functional `setSearchParams(updater)`
queued in a single React tick the same pre-batch snapshot, so a `setFilter`
+ `setPage` issued from the same event handler (e.g. a debounced filter
commit landing alongside a paging click) would collapse — the second
write erased the first and `q` disappeared from the URL. The earlier
window.location workaround papered over the symptom for the typical case
but didn't remove the underlying possibility.

Replace both hooks with `useTableState`, which owns filter + pageIndex +
storage roundtrip together and routes every URL write through a single
microtask-coalesced `update(patch)`. When multiple `update` calls fire in
the same tick — `setFilter` and `setPage`, two synchronous handlers, an
effect and a click — they merge into one navigation rather than racing.
Replace conflicts resolve in favour of push, so intentional history
entries (paging) survive coalescence with replace-only updates (filter
typing). The `MemoryRouter` fallback (latest snapshot via ref) is kept
only as a defensive read; the coalescence itself makes it redundant.

A regression test (`two top-level updaters firing in the same tick keep
both params`) locks this in: the previous behaviour failed it, the new
implementation passes it without touching `window.location`.

Read-only siblings stay on `useTableQueryFilterReader` — detail pages
don't write the URL, so no race surface to remove. `usePagination`
deleted entirely; its callers migrated to `useTableState`.

474/474 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:26:14 +07:00
Sergey KozyrenkoandClaude Opus 4.7 019ddcb1a7 fix(frontend): preserve URL params across batched setSearchParams updates
When a debounced filter commit and a paging button click land in the same
React tick, react-router v6 feeds the functional updater of every queued
`setSearchParams` call the same pre-batch snapshot. The second write
overwrites the first instead of stacking, so `setFilter('foo')` followed
by `setPage(5)` collapses to `?page=6` and `q` is lost. The QA pass
caught this on /flows: clicking >> right after typing in the filter
silently dropped `?q=` from the share-able URL.

Build the next `URLSearchParams` from `window.location.search` instead,
since that's the freshest source of truth under BrowserRouter and
sidesteps react-router's batching entirely. Fall back to the latest
react-router snapshot (stashed in a render-synced ref) under
MemoryRouter, which the hook tests use — `MemoryRouter` doesn't sync
its in-memory history to `window.location`.

478/478 existing tests still pass; the browser repro that previously
produced `?page=6` now correctly produces `?q=bypass&page=6`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 20:48:06 +07:00
Sergey Kozyrenko cd0fff34f3 fix 2026-05-16 20:30:38 +07:00
Sergey KozyrenkoandClaude Opus 4.7 ca32c199e8 refactor(frontend): compose DataTable storageKey via usePageStorageKeys
Hard-coding the full `table_4_/settings/prompts:agents` / `:tools` keys in
the page duplicated the route prefix that `usePageStorageKeys` already
computes — if the prefix ever bumps (e.g. `table_4_` → `table_5_` on a
storage migration), every call site silently goes stale. Read the route
base from the hook instead and only own the per-table suffix in the page.
Updated the `storageKey` JSDoc on DataTable to document this composition
pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 20:28:15 +07:00