Commit Graph
290 Commits
Author SHA1 Message Date
Sergey Kozyrenko 1debdd9fdf fix 2026-05-20 01:25:18 +07:00
Sergey KozyrenkoandClaude Opus 4.7 ed066603c2 style(frontend): drop redundant Search icon, stretch Flags column header
- DataTableFilter no longer renders a leading `<Search>` addon. With
  `InputSearch` now living in the same page header as the trigger for
  semantic search, two magnifier icons next to each other read as
  visual clutter — the placeholder text alone already cues the input's
  purpose. Drops the unused `Search` import too.
- The "Flags" column header span gets `w-full` so its
  `justify-end` actually pushes the label flush against the right edge
  of the cell. Without the explicit width it collapsed to content
  size and the alignment had no axis to act on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:45:10 +07:00
Sergey KozyrenkoandClaude Opus 4.7 5f7b683974 fix(frontend): keep InputSearch typing responsive via local-state mirror
Typing fast into the knowledges header search dropped characters: each
keystroke went straight into the parent's `onSearchChange`, which on
this page funnels through `setSearchParams` → router re-render → the
controlled `searchQuery` prop snapped back through React. When the
round-trip outran the user's fingers, the input's visible value rolled
back and chars between commits were lost ("find" → "fn").

Mirrored the `DataTableFilter` pattern: hold a `localValue` state that
keystrokes update synchronously, debounce the commit upstream
(`useDebouncedValue`, 150 ms — same constant as the table filter so
both inputs on this page feel uniform), and reconcile external resets
(Esc, programmatic URL wipe, back button) via a `lastEmittedReference`
guard that ignores our own round-trips.

Blur and Escape now also read `localValue` instead of `searchQuery` so
keystrokes pending in the debounce window aren't treated as "nothing
typed yet". Escape additionally fast-paths an empty commit upstream so
the clear hits the parent without waiting for the debounce.

Measurements (chrome-devtools, 19-keystroke burst): pre-fix typing
dropped characters, post-fix avg keystroke 2.07 ms, p95 6 ms, 0
keystrokes over the 16 ms 60-fps budget. Server `searchKnowledge`
still fires exactly once per typing pause.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 18:43:09 +07:00
Sergey KozyrenkoandClaude Opus 4.7 747c988556 style(frontend): tweak Kbd tooltip palette, drop redundant Plus sizing
- Kbd inside tooltip-content now uses the `accent` token family
  (`bg-accent/20`, `text-accent-foreground`, dark `bg-accent/50`) for
  better legibility against the popover background. The previous
  `bg-background/20` chips read as thin film on dark tooltips.
- The empty-state "New Knowledge" button on the knowledges page passes
  `<Plus />` without an explicit `size-4` — the Button variant already
  sizes its icon slot, the override was duplicating defaults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 18:27:43 +07:00
Sergey KozyrenkoandClaude Opus 4.7 8ed62a24e8 fix(frontend): emit shadcn v4 data-slot attrs on Tooltip primitives
Children like Kbd target tooltip surfaces via
\`[[data-slot=tooltip-content]_&]:bg-background/20\` selectors so the
chips can re-tone themselves for the dark popover background. Our
TooltipContent was missing that attribute (the file shipped from a
pre-v4 cut of shadcn), so the override never matched and Kbd kept its
muted page-surface styling — invisible on the tooltip.

Set \`data-slot\` on every Tooltip primitive (provider / root /
trigger / content) to match the v4 contract. Verified in DevTools:
Kbd inside tooltip-content now computes the inverted background and
foreground colors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 18:18:25 +07:00
Sergey KozyrenkoandClaude Opus 4.7 992eb8b198 refactor(frontend): swap InputSearch tooltip text for shadcn Kbd chips
shadcn ships the canonical Kbd-inside-Tooltip pattern (per the
upstream kbd-tooltip demo and our existing components/ui/kbd.tsx).
The Kbd component already self-themes for tooltip-content surfaces
via `[[data-slot=tooltip-content]_&]` overrides, so swapping the
plain "(⌘ K)" suffix for `<KbdGroup><Kbd>⌘</Kbd><Kbd>K</Kbd></KbdGroup>`
matches the rest of the app and renders proper keycap chips against
the tooltip background.

Verified the accessibility-description path: Radix still surfaces the
flattened "Semantic search... ⌘ K" string to screen readers (we saw
it in the a11y snapshot), so the keyboard hint stays announced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 18:11:13 +07:00
Sergey KozyrenkoandClaude Opus 4.7 e2cd75720e feat(frontend): show placeholder + hotkey tooltip on InputSearch trigger
The collapsed trigger is just a magnifier icon — there's no visible
hint about what it does or which key reveals it. Added a tooltip that
combines the `placeholder` with the platform-appropriate hotkey glyph
(⌘ on Apple, Ctrl elsewhere) using the existing `isMac()` helper. When
`hotkey` is `null` the suffix drops and the tooltip shows just the
placeholder.

Tooltip wraps only the trigger button: once the input is expanded the
caret is in it and a hovering tooltip would obscure the very thing the
user is typing into.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 18:04:32 +07:00
Sergey KozyrenkoandClaude Opus 4.7 dd786d4c9d fix(frontend): restore InputSearch blur-collapse for empty value
The original sofy-front version collapsed the control whenever focus
left an empty input. When the duplicate blur+effect pair was folded
into a single effect during the port, that behaviour silently went
with it — opening the trigger without typing and then clicking
elsewhere left the box expanded forever, because `searchQuery` never
changed and the effect never re-ran.

Re-added a dedicated `onBlur` handler that collapses on focus loss
when the value is empty. The effect stays as the safety net for the
programmatic-clear case (parent wipes `?qs=` from the URL while focus
is elsewhere) — different trigger, different path, neither path can
cover the other.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 18:00:40 +07:00
Sergey KozyrenkoandClaude Opus 4.7 5a7b2e882e style(frontend): align InputSearch sizing with size='sm' button geometry
`HeaderButton` defaults to `size='sm'` (h-8 px-3 rounded-md text-xs)
and pages mount InputSearch right next to it. The component previously
shipped with h-7 / icon-xs (28 px) so the icon-only trigger sat a
notch shorter than its sibling action — visible in the knowledges
header.

Bumped the group to h-8, the trigger to size='icon-sm', and pinned
the inner input to h-8 (it inherits `Input`'s default h-9 otherwise,
which pokes 4 px past the bordered group). Dropped the duplicate
negative margins on the button — the addon's
`has-[>button]:ml-[-0.45rem]` already handles flush-left alignment
when expanded; only the collapsed state needs the explicit `-mx-1.5`
to claw back the addon's default padding around the lone icon.

Verified: trigger 32×32, group 32 px, input 32 px, all matching the
neighbouring `HeaderButton` baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 17:55:54 +07:00
Sergey KozyrenkoandClaude Opus 4.7 31a7f34c0f feat(frontend): add collapsible InputSearch, wire it to ?qs= on knowledges
New `components/ui/input-search.tsx`: a controlled, collapsible search
control that idles as just a `<Search>` icon button and expands into a
text input with a motion-driven width animation. Adapted from the
sofy-front version with:

- Trigger is a real `<button>` (via `InputGroupButton`) instead of a
  `<div onClick>` — keyboard reachable, screen-reader announced, and
  avoids the invalid-HTML nesting that putting an `<input>` inside a
  `<button>` would create.
- Configurable Mod+`hotkey` (default `'f'`, `null` to disable). The
  knowledges page uses `'k'` so it doesn't fight the browser's own
  Ctrl+F find-in-page.
- Escape clears the value on the first press, blurs + collapses on the
  second — matches native search-input muscle memory.
- Single collapse effect on `searchQuery` change instead of the
  duplicate blur+effect pair.
- `aria-label` prop wired to both the trigger button and the input.

The knowledges header now mounts this control bound to `?qs=` via
`useSearchParams`. The provider already debounces the URL value before
hitting `searchKnowledge`, so the input commits keystrokes immediately
(instant UI feedback) while the server roundtrip waits for the 400 ms
debounce. URL writes use `replace` to avoid history-stack churn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 17:40:11 +07:00
Sergey KozyrenkoandClaude Opus 4.7 4430cfc329 fix(frontend): suppress ?q= storage restore when ?qs= is present
When `/knowledges` is opened via a `?qs=…` link, `useTableState`'s
mount-time restore was injecting the previous session's `?q=` from
storage on top of it — so a clean semantic-search URL silently became
`?qs=foo&q=python` and the user got a narrowed result set they didn't
ask for.

Added `skipRestore` to `useTableState` (defaults to `false`, captured
via a ref at mount so a later toggle doesn't replay a restore the user
has worked past). `knowledges.tsx` passes `searchParams.has('qs')` so
the restore is skipped exactly when the parameter is part of the
landing URL.

Mirror-to-storage on subsequent filter changes is unaffected — a
client filter the user actually types still persists to storage for
the next session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 13:16:25 +07:00
Sergey KozyrenkoandClaude Opus 4.7 2a4417505a feat(frontend): wire knowledges provider to searchKnowledge via ?qs=
Knowledges list previously sourced everything from `knowledgeDocuments`.
When `?qs=` is present the provider now switches to `searchKnowledge`
(debounced 400ms, capped at 100 results) and feeds the resulting subset
through the same public `knowledges` array — so DataTable, the existing
`?q=` client filter, and DetailNavigation continue to walk it without
any consumer-side change.

Subscription cache merge in `lib/apollo.ts` only covers the
`knowledgeDocuments` root field; in search mode CRUD events trigger a
`refetchQueries(['searchKnowledge'])` via `useLatestRef` so the relevance
ordering stays consistent without freezing the boolean at mount.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 12:48:55 +07:00
Sergey KozyrenkoandClaude Opus 4.7 a55f7bd6eb feat(frontend): add Copy Token ID action to api-tokens row dropdown
Mirrors the entry already present in the right-click context menu so
both menus expose the same set of actions (Edit / Copy Token ID /
Delete) — the dropdown was previously missing the copy shortcut.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 11:35:48 +07:00
Sergey KozyrenkoandClaude Opus 4.7 d08295610a fix(frontend): suppress touch text-selection on rows with a context menu
Long-press on iOS/Android starts native text selection before Radix
ContextMenu reacts, so the row appears highlighted from the top of the
table down to the finger. Apply `user-select: none` and disable the iOS
callout on those rows only, and only on coarse pointers — desktop users
keep the ability to select cell text with the mouse.

Pattern matches the workaround discussed in radix-ui/primitives#930.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 11:35:39 +07:00
Sergey KozyrenkoandClaude Opus 4.7 85a7ee281f fix(frontend): keep table row highlighted while context menu or dropdown is open
Hover relies on the cursor staying over the <tr>, but Radix portals the
overlay so the row dims as soon as the menu opens. Match the same
background via `data-[state=open]` (ContextMenuTrigger forwards
`data-state="open"` onto the row through `asChild`) and
`has-[[data-state=open]]` (covers any Dropdown/Popover triggered from a
cell). Switch the selection prop to a conditional spread so the prior
`data-state={false}` no longer wins over Radix Slot's merged props.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:44:03 +07:00
Sergey KozyrenkoandClaude Opus 4.7 f369b411ed refactor(frontend): centralize document <title> in a single registry
Single source of truth for every route's tab title. All title knowledge —
static strings, params-derived resolvers, and Apollo-cache-driven reactive
components — now lives in src/lib/route-titles/, and app.tsx imports only
the `routeTitles` object. The router file no longer pulls Apollo hooks
or formatters; it just spreads handles onto routes.

Handle API collapsed from two keys to one. Following the canonical
`useMatches` example from React Router docs, `handle.title` is now a
single field that accepts three forms:

  • `string` — static, known at build time
  • `(params) => string` — synchronous resolver from URL params
  • `ComponentType<{ params }>` — reactive (e.g. Apollo cache subscription)

DocumentTitle distinguishes resolver vs component by the PascalCase
convention (component names start with an uppercase letter), so the
choice is implicit at the call site — no `titleComponent` field, no
discriminated union, no marker property.

Boilerplate consolidation:
- 4 near-clone resource-title components (Flow/Knowledge/Provider/
  Template) collapse into 4 declarative `apolloTitle({ useQuery,
  variables, select })` calls in the registry. The factory owns the
  `fetchPolicy: 'cache-only'` plumbing, skip semantics, and component
  naming.
- renderTitle/APP_NAME/RouteParams were duplicated between
  document-title.tsx and resource-titles.tsx — now imported from one
  render-title.tsx.
- formatPromptId moves into the route-titles module since the prompt
  detail page and the route handle are its only consumers.

References to React docs added to document-title.tsx: only one <title>
should be rendered from React at a time (multiple → undefined browser/
SEO behavior). The convention is now enforceable from one place.

All 482 unit tests pass; lint, tsc, prettier clean. Verified in the
browser across every route — static (Dashboard, Flows, Resources, …),
/new variants (New flow, New provider, …), params resolver
(/settings/prompts/agentSelector → "Agent Selector"), and Apollo
reactive (/flows/491 → "Flow #491 — Login Beta Program"), including
sibling navigation between detail routes which previously motivated
moving DocumentTitle into the shell.

Net diff: -171 / +66 across the touched files plus the new registry.

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 73bb0e6446 fix(frontend): toast + redirect when knowledge document not found
Match the redirect pattern used on the flow page: instead of rendering
a dedicated "not found" card with a manual back button, show a toast
and replace the route with /knowledges so the user lands on the list
the same way other resources behave.

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 f2fb222807 refactor(frontend): collapse PageTitle into DocumentTitle via route handles
Single mechanism for the document <title>. Every route now declares its
title in the react-router handle:

  • handle: { title: 'Dashboard' } — static listing pages
  • handle: { title: (params) => formatPromptId(params.promptId) } —
    params-derived (e.g. /settings/prompts/:promptId)
  • handle: { titleComponent: TemplateTitle } — Apollo cache-driven,
    reactive to cache updates after the page mounts

A single <DocumentTitle/> in RootLayout walks useMatches() and picks
the deepest match. PageTitle is gone — 13 call sites across 13 page
components migrated to handle, the component deleted.

Other improvements:
- Split document-title.tsx so the shell-only <DocumentTitle/> has zero
  graphql imports — the resource title components (FlowTitle,
  KnowledgeTitle, ProviderTitle, TemplateTitle) live in
  resource-titles.tsx. This lets the unit tests render DocumentTitle
  without dragging the codegenerated graphql/types.ts through Vite.
- String(p.id) === providerId replaces sloppy == in ProviderTitle.
- matches.findLast(...) replaces the imperative for-loop.
- Extracted formatPromptId into lib/utils/format-prompt-id.ts so the
  prompt detail page and the route handle resolver share one formatter.

7 unit tests added for DocumentTitle covering: static title, derived
title from params, deepest-match-wins, titleComponent rendering,
titleComponent precedence over static title, empty-string fallback,
and no-handle fallback.

19 test files / 482 tests pass; lint + tsc clean.

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 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