Commit Graph
196 Commits
Author SHA1 Message Date
Sergey KozyrenkoandClaude Opus 4.7 c196e55d5d feat(frontend): mobile-friendly headers across detail and list pages
Roll the truncate-chain + mobile-navigation pattern introduced for the
/flows/:id page out to every other detail and list page so a long title
never pushes the action buttons off-screen on a ~390px viewport.

Detail pages
- `templates/template.tsx` and `features/knowledges/knowledge-header.tsx`
  now mirror `pages/flows/flow.tsx`: left container becomes
  `flex min-w-0 flex-1` with `shrink-0` on the SidebarTrigger/Separator,
  Breadcrumb/BreadcrumbList/BreadcrumbItem get the `min-w-0` /
  `flex-nowrap` chain, BreadcrumbPage gets `truncate`, the right action
  area becomes `flex shrink-0`. InlineEditInput in both pages now uses
  `w-64 min-w-0 max-w-full flex-1` so the rename input shrinks with the
  parent instead of forcing it to ≥256px.
- On `isMobile`, the DetailNavigationToolbar is hidden and re-surfaced
  inside the existing actions dropdown as a single row matching
  `pages/flows/flow.tsx` (icon + label + Prev/Position/Next button
  group with shared borders; middle button doubles as the sheet
  trigger). DetailNavigationSheet is mounted separately, controlled by
  `isMobileNavSheetOpen` state.

List pages
- `pages/flows/flows.tsx`, `pages/knowledges/knowledges.tsx`,
  `pages/templates/templates.tsx`, `pages/resources/resources.tsx`,
  and `pages/flows/new-flow.tsx` get the same header truncate chain
  preventively — most page titles are short, but the structure stays
  consistent across pages and protects against future longer labels.
- `pages/templates/templates.tsx` Title cell gains `max-w-[380px]
  truncate` (matched to the existing Text-cell cap) so a long template
  title can't blow up the row.
- Settings pages (`settings-providers.tsx`, `settings-api-tokens.tsx`)
  don't render a breadcrumb header — their `SettingsLayout` already
  shows the sidebar trigger — but their inline header rows still leaked
  on narrow screens. Add `min-w-0 flex-1 truncate` to the description
  text and `shrink-0` to the Create button so the right-edge CTA stays
  visible while the left text degrades gracefully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:21:25 +07:00
Sergey KozyrenkoandClaude Opus 4.7 7a2f882cea feat(frontend): mobile-friendly flow detail header and unified picker
Make the /flows/:id page usable on a 390px viewport and replace the two
parallel template/attachment dropdowns with one trigger that works the
same way on every screen.

Header
- Breadcrumb chain (`flex min-w-0 flex-1` on the left container, `flex
  shrink-0` on the right one, `min-w-0 flex-nowrap` on BreadcrumbList,
  `min-w-0 gap-2` on BreadcrumbItem, `truncate` on BreadcrumbPage) so the
  flow title can shrink past its intrinsic width instead of pushing the
  action buttons off-screen.
- InlineEditInput: `w-64 min-w-0 max-w-full flex-1` — keeps the 256px
  default on desktop but lets the rename input collapse to whatever space
  is left on narrow viewports.
- On `isMobile`, the desktop DetailNavigationToolbar and the favorite Star
  button are hidden and re-surfaced inside the Flow-actions dropdown:
  a single row matching the theme-menu pattern in `main-sidebar.tsx`
  (icon + label + Prev/Position/Next button group sharing borders), plus
  a separate "Add/Remove favorites" item. The position button doubles as
  the sheet trigger, so the mobile navigation has the same affordances as
  the desktop toolbar.

Flow form
- Templates and Resources (formerly "Attachments") share one Ellipsis
  trigger placed next to the Send button on every viewport — the two
  separate FileText/Paperclip dropdowns are gone.
- Inside the dropdown, Radix `<Tabs>` switches between picker panels
  rendered above the tab strip; the strip lives at the bottom so it
  lands next to the trigger. The dropdown opens upward (`side="top"`
  with `align="end"`) and has a fixed `w-72` so it stays inside the
  viewport on phone-width screens.
- Tab switch is deferred one tick (`setTimeout(..., 0)`) before
  mutating `pickerTab`. Radix DropdownMenuItem listens to `pointerup`
  directly, so a synchronous swap let the pointerup that ended the tab
  click land on the freshly mounted "Upload files" item in the Resources
  panel and fired its `onSelect`.
- Send button gets `shrink-0`; combined trigger picks up `ml-auto` so
  Send/trigger stay glued to the right edge without two `ml-auto` items
  fighting over leftover space.

Detail navigation primitives
- Expose `DetailNavigationSheet` and `useNavigation` from the package
  index so a page can compose its own mobile UI without re-implementing
  the filtered-subset / Prev-Next algorithm.
- `DetailNavigationSheet`: add `pr-8` to `SheetTitle` so the trailing
  total counter (e.g. "311") stops sitting underneath the absolutely
  positioned close button on the right edge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:03:26 +07:00
Sergey Kozyrenko 829271e7e4 refactor(frontend): inline useControllable into autocomplete.tsx
The hook had exactly one consumer (components/ui/autocomplete.tsx) and
was a tiny ~20-line Radix-style controlled/uncontrolled state mirror.
Move it inline as a private helper next to the component that uses it,
so a future reader doesn't have to chase a one-call hook through hooks/.
useLatestRef stays in @/hooks/ — it's genuinely cross-cutting.
2026-05-15 12:41:56 +07:00
Sergey Kozyrenko e041b11cef refactor(frontend): move table-filter into detail-navigation/ as text-filter
The createTextMatcher helper has only one production consumer
(detail-navigation/use-navigation.ts) and the original 'table-filter'
name implied a tie to <DataTable>'s column filter that the codebase
never actually wired (DataTable still uses TanStack's built-in
'includesString'). Move the file into detail-navigation/ as a private
text-filter module so the matcher lives next to its only caller and the
name reflects what it does (text matching) rather than aspirational
table-filter alignment.

If the table column filter is ever switched to use createTextMatcher
for diacritic folding, the import would simply move back up — but
that's a separate behaviour change, not a naming concern.
2026-05-15 12:34:42 +07:00
Sergey Kozyrenko 33dba9dbf4 refactor(frontend): inline cycleColumnSort into data-table.tsx
Move the lone cycleColumnSort function from lib/table-sort.ts straight
into components/ui/data-table.tsx — it was a one-consumer helper used
only by DataTableColumnHeader, so co-locating it removes the
cross-folder hop without changing behaviour. Exported alongside
DataTableColumnHeader for parity with the shadcn convention and so a
future custom header can reuse the cycle. The four unit tests move
into data-table.test.tsx as a sibling describe block; lib/table-sort
is gone.
2026-05-15 12:22:32 +07:00
Sergey Kozyrenko 7a6d50a1c1 refactor(frontend): relocate report module to lib/report/ folder
Move features/report/ → lib/report/ — the module is mostly pure
utilities (generateReport, generateFileName, copyToClipboard,
downloadTextFile) plus an internally lazy-loaded React-pdf component,
not a feature with its own domain/provider/queries. lib/ is the
honest home; the folder grouping addresses the original audit
complaint of a 658-line .tsx loose in lib/. Consumers swap
'@/features/report' for '@/lib/report'.
2026-05-15 12:06:55 +07:00
Sergey Kozyrenko 31c894ee65 refactor(frontend): move report module from lib/ to features/report/
The PDF generator (~658 lines, lazy-loaded) and its public-API wrapper
report.ts are functionally one module. They now live together in
features/report/ instead of being scattered in lib/, which was a poor
home for a 658-line React-pdf component anyway. The lazy-import path
inside report.ts stays relative (./report-pdf), and the four consumers
(flow.tsx, flow-files.tsx, flow-report.tsx, resources.tsx) just swap
'@/lib/report' for '@/features/report'.

Note: copyToClipboard and downloadTextFile come along for the ride —
they're generic helpers used in non-report contexts too. Splitting them
out cleanly into lib/ would be worth a follow-up if you want stricter
domain boundaries.
2026-05-15 11:52:02 +07:00
Sergey Kozyrenko f46de8d374 refactor(frontend): consolidate inline-edit module and drop the rename framing
Group inline-rename-input.tsx and use-inline-edit-title.ts into a new
components/shared/inline-edit/ folder, the same pattern we applied to
detail-navigation, overwrite, and unsaved-changes. Rename to drop the
misleading "rename" framing — the API has nothing rename-specific
(autoFocus, busy, defaultValue, onCancel, onSave) and the same component
is used for quick-create and inline-edit flows too:
  InlineRenameInput → InlineEditInput
  useInlineEditTitle → useInlineEdit
  INLINE_RENAME_MAX_LENGTH → inlined as default `maxLength = 200`
2026-05-15 11:41:52 +07:00
Sergey Kozyrenko c75335eac9 refactor(frontend): inline DataTableColumnHeader into data-table.tsx
Fold the standalone SortableColumnHeader into components/ui/data-table.tsx
and rename it to DataTableColumnHeader (the canonical name from
ui.shadcn.com/docs/components/data-table). The two pieces only ever
shipped together — every consumer paired SortableColumnHeader with
DataTable inside the TanStack column definition. Keeping data-table as
a single file matches the convention already used elsewhere in
components/ui/. Also rename the prop label → title to match the upstream
shadcn signature; touches 20 JSX usages across 5 list pages.
2026-05-15 10:46:46 +07:00
Sergey Kozyrenko d8e84d0df7 refactor(frontend): rename useOverwriteAction to useOverwrite
Inside `components/shared/overwrite/` the folder name already supplies
context, so the `-action` suffix is redundant — same principle that
turned overwrite-confirm-dialog into overwrite-dialog. Rename file
use-overwrite-action.ts → use-overwrite.ts, export useOverwriteAction
→ useOverwrite, and the internal UseOverwriteAction* prop/result types
to match. The shape lines up with the project's verb-noun action-hook
convention (useResourcesCopy, useResourcesMove).
2026-05-15 10:09:48 +07:00
Sergey Kozyrenko 90eba577b2 refactor(frontend): drop verbose suffixes from overwrite module names
Within `components/shared/overwrite/` the folder name already supplies
context, so the file/component names don't need to repeat it. Rename
overwrite-confirm-dialog → overwrite-dialog and overwrite-cta-buttons →
overwrite-buttons (and matching exports OverwriteConfirmDialog →
OverwriteDialog, OverwriteCtaButtons → OverwriteButtons) so the module
matches the file-and-export naming convention used by detail-navigation.
2026-05-15 10:03:49 +07:00
Sergey Kozyrenko 4d2e4ff5bf refactor(frontend): consolidate overwrite and unsaved-changes into module folders
Group the overwrite trio (OverwriteConfirmDialog, OverwriteCtaButtons,
useOverwriteAction) into components/shared/overwrite/ — they cross-import
the OverwriteConflict type and 12 consumers always pull from the same
logical module. Same pattern for the unsaved-changes pair: move the
dialog from components/shared/ and the UI-agnostic guard hook from
hooks/ into components/shared/unsaved-changes/. Each folder ships an
index.ts so consumers reach the module through one import path.
2026-05-15 09:50:08 +07:00
Sergey Kozyrenko ef0dbcf0e2 refactor(frontend): consolidate detail-navigation into a single module folder
Move the Prev/Position/Next toolbar and its supporting hooks from scattered
locations under components/shared/ and hooks/ into a single
components/shared/detail-navigation/ folder. Rename ListNavigation* →
DetailNavigation* (toolbar/buttons/sheet) so the names reflect what they
actually do — sibling navigation between detail pages, not navigation
within a list. The internal algorithmic hook drops its now-redundant
prefix (useFilteredListNavigation → useNavigation, computeListNavigation →
computeNavigation), and useDetailNavigation moves alongside the toolbar
since it's the public composition layer that produces toolbarProps.
2026-05-15 09:33:12 +07:00
Sergey KozyrenkoandClaude Opus 4.7 21d61b37ee perf(frontend): lazy-load @react-pdf/renderer and drop dead html2pdf.js
- @react-pdf/renderer (~1.5 MB) moved to an async chunk. lib/report.ts
  used to statically re-export PDF helpers from ./report-pdf, which
  pulled the entire PDF library into every page that imports
  lib/report (flow.tsx, flow-report.tsx). The re-exports are now
  async wrappers using `await import('./report-pdf')`, so the
  library loads only when the user actually triggers a PDF export.
  Initial JS for /flows/:id/report drops from ~2.0 MB to ~500 KB.
- Drop html2pdf.js: zero imports anywhere in the source, but it was
  declared as a dependency and reserved a (now-empty) manual chunk in
  vite.config.ts. Removing it also drops 22 transitive packages from
  the lockfile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:38:07 +07:00
Sergey KozyrenkoandClaude Opus 4.7 420944946e style(frontend): apply prettier formatting
Whitespace, line wrapping and Tailwind class ordering only — no code
changes. Brings 11 files in line with the prettier + tailwindcss
plugin config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 06:55:28 +07:00
Sergey KozyrenkoandClaude Opus 4.7 abe45e33c9 chore(frontend): rename Cyrillic clipboard.ts and remove dead code
- src/lib/сlipboard.ts → clipboard.ts: the filename used a Cyrillic 'с'
  (U+0441) instead of Latin 'c', which made the path silently
  unmatchable by anyone typing the import with a Latin letter. Update
  the 4 importers in features/flows.
- Delete unreachable settings-mcp-server(s).tsx (~1.3k lines): no
  routes registered in app.tsx and nothing imports them. Remove the
  matching dead branches and commented menu entry from
  settings-layout.tsx.
- Drop unused dev deps simple-git-hooks and lint-staged: no config in
  the repo, not used by CI or Docker, hooks were never wired up. Also
  remove simple-git-hooks from pnpm.onlyBuiltDependencies.
- Drop unused tsconfig path aliases: @/ui/*, @env (env.ts doesn't even
  exist), and @pkg. Only @/* remains.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 06:53:53 +07:00
Sergey KozyrenkoandClaude Opus 4.7 692d91cffa fix(frontend): sync URL on pageSize change, label knowledge columns
- DataTable: route handlePageSizeChange through handlePaginationChange so
  onPageChange fires when picking "All" on a high page drops pageIndex to 0.
  Without this, the URL kept a stale ?page=N while the display correctly
  clamped to "Page 1 of 1".
- DataTable: reconcile effect in controlled mode now compares
  externalPageIndex (the URL source of truth) against safePageIndex instead
  of the internal mirror, which can drop to 0 via handlePageSizeChange and
  hide the URL mismatch from the previous comparison.
- knowledges: add meta.columnMenuLabel to docType and question columns so
  the Columns dropdown reads "Type" / "Question" — same labels as the
  table headers — aligning with the Flags/Preview entries.
- data-table.test: cover both pathways — out-of-range controlled pageIndex
  on mount, and pageSize=All from page 2.
- vitest.setup: polyfill hasPointerCapture / setPointerCapture /
  releasePointerCapture so Radix Select interactions don't crash in jsdom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:46:17 +07:00
Sergey KozyrenkoandClaude Opus 4.7 a8a7431613 fix(frontend): clamp page index, sync settings filters, harden inline rename
- DataTable: derive a clamped `safePageIndex` from URL + `pageCount`; use it
  for display + reconcile URL via effect when raw differs. Fixes "Page 999
  of 31" and "Showing 9981–307 of 307" when a hand-typed URL, a filter
  narrowing the dataset, or a pageSize=All bump leaves the URL out of range.
- DataTable: only persist `pageSize` to storage when it differs from
  `initialPageSize` so a fresh mount no longer seeds `{ pageSize: 10 }` via
  StrictMode dev double-invoke.
- useTableQueryFilter: storage replay no longer drops `?page=` — that flag
  belongs to user-driven `setFilter`, not to restoration of prior state.
  Out-of-range pages after replay are now handled by the clamp above.
- Settings/Providers + Settings/API tokens: pass `filterValue` / `onFilterChange`
  from `useTableQueryFilter` so the filter input is URL- and storage-synced,
  matching the pattern used by flows, knowledges, templates, resources.
- InlineRenameInput: export `INLINE_RENAME_MAX_LENGTH = 200` and apply it as
  the native `maxLength` attribute. UX guard against paste-bombs that would
  break truncation; `defaultValue` is untouched so existing long records
  remain editable.
- knowledges: render a "Flags" header for the badges column instead of `null`,
  aligning with `meta.columnMenuLabel` and other column headers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:48:15 +07:00
Sergey KozyrenkoandCursor 5319d1cb65 feat(frontend): unify list tables with URL sync, navigation, and Vitest
- Add table-state, url-params, table-filter, view-options-storage utilities
- Add hooks: pagination, table query filter, filtered list/detail navigation, inline title edit, page storage keys
- Add shared list navigation toolbar/sheet/buttons, sortable headers, inline rename input
- Refactor DataTable; integrate flows/knowledges/templates/resources/settings pages
- Remove adaptive column visibility and legacy table-storage
- Configure Vitest with tests for hooks, lib, and components

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 21:36:14 +07:00
Sergey KozyrenkoandCursor fff79b3979 feat(knowledges): inline rename and delete actions in knowledge header and list
Adds an actions dropdown to the knowledge detail header with inline
rename (double-click or "Rename") and delete with a confirmation dialog.
The form keeps any unsaved edits when an external rename refreshes the
cache (`keepDirtyValues` + reactive `values`), and `onBeforeNavigateAway`
suppresses the unsaved-changes guard right after a successful delete.

The knowledges listing gets the same: a "Rename" item in the row
dropdown and context menu opens an in-row editor, and clicking the row
no longer navigates while a rename is in progress.

Drops the redundant `knowledgeName` prop that was threaded through
`KnowledgeForm`/`KnowledgeLayout`/`KnowledgeHeader` — the header now owns
the title and computes it locally from `knowledge.question`.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 20:52:51 +07:00
Sergey KozyrenkoandCursor bce4d79069 feat(templates): inline rename and delete actions in template header and list
Adds an actions dropdown to the template detail header with inline rename
(double-click or "Rename") and delete with a confirmation dialog. The
listing learns the same: a "Rename" item in the row dropdown and context
menu opens an in-row editor, and clicking the row no longer navigates while
a rename is in progress.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 20:52:42 +07:00
Sergey KozyrenkoandCursor 465ea406f4 feat(flows): inline rename and finish/delete actions in flow header
Brings the flow detail page in line with the actions row already available
on the listing: double-click or pick "Rename" from the new actions menu to
edit the title in place, "Finish" to gracefully stop a running flow, and
"Delete" with a confirmation dialog. The listing keeps a `PencilLine`
icon for "Rename" so the action looks consistent across surfaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 20:52:35 +07:00
Sergey KozyrenkoandCursor b97a0c10a5 feat(header): collapse page header buttons to icons on mobile
Add `HeaderButton` component that renders icon + label on >=768px and
collapses to an icon-only square on narrower viewports, with the
`aria-label` auto-derived from the label so the mobile state stays
accessible. Apply it to the action buttons in Flows, Flow, Knowledges,
Templates, Resources and the Knowledge save button so mobile page
headers stop overflowing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 14:18:59 +07:00
Dmitry Ng 2f4da118e0 feat(anonymization): implement anonymizeText mutation and associated service
- Added a new SQL migration to insert the 'anonymize.call' privilege into the privileges table.
- Introduced the `anonymizeText` mutation in the GraphQL schema, allowing users to anonymize sensitive text.
- Implemented the `AnonymizerService` to handle text anonymization requests via a REST API endpoint.
- Updated the GraphQL resolver to integrate the new mutation and ensure proper permission checks.
- Enhanced documentation with Swagger and OpenAPI specifications for the new endpoint.
- Added error handling for invalid requests and unavailable anonymizer configurations.
2026-05-12 21:47:40 +03:00
Dmitry Ng fbf917a18a fix(aslog, msglog): remove message length truncation logic
Eliminate the message length truncation from both `putMsg` methods in `aslog.go` and `msglog.go`. This change simplifies the message handling process by allowing messages to be processed without arbitrary length restrictions.
2026-05-12 21:37:16 +03:00
Dmitry Ng 0f0a7bd2d0 fix(database/knowledge): replace SimilaritySearch with direct SQLC vector queries
- Fix empty ID bug: langchaingo SimilaritySearch discarded document UUIDs; new SearchKnowledgeDocuments/SearchUserKnowledgeDocuments return them directly
- Remove unsafe fmt.Sprintf SQL filter interpolation, use parameterised queries
- Exclude memory documents from search results at SQL level
- Add FlowID support to passesSearchFilter
- Convert all $N positional params to sqlc.arg(name) across knowledge queries
- Update tests: replace TestBuildSearchFilters with TestPassesSearchFilter, add TestSearchDocuments and TestSearchUserDocuments
2026-05-12 20:47:46 +03:00
Sergey KozyrenkoandCursor ad3b5062e1 fix(autocomplete): keep cmdk a11y attributes in sync with popover state
cmdk renders a sr-only `<label cmdk-label for={cmdkUseId}>` inside
`<Command>` and points its `for` at a useId it generates for *its own*
input. Our visible input gets its id from `FormControl`'s Radix Slot
instead, so the two ids never line up and Chrome flags "Incorrect use
of `<label for=FORM_ELEMENT>`". Re-point the `for` at the real input id
in a `useLayoutEffect` so the HTML-level association is valid — cmdk
already wires `aria-labelledby` to the same element through Slot, so
screen-reader behaviour is unchanged.

cmdk also hard-codes `aria-expanded={true}` on its combobox input, so
the attribute lies whenever the popover is closed (after Enter,
Escape, outside-click). Pass `aria-expanded={open}` on the inner
`<Input>` — Radix Slot merges child props over slot props for
non-handler attributes, so our value wins and keeps the ARIA state in
sync with the actual popover state.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 23:11:05 +07:00
Sergey KozyrenkoandCursor ba63992e88 feat(autocomplete): substring filter and code-language suggestions
Default the autocomplete filter to a case-insensitive substring match
instead of cmdk's fuzzy `command-score`: in plain autocomplete usage,
returning items whose characters merely appear in order is surprising.
Consumers can still opt back into fuzzy matching via the `filter` prop.

Extract `useControllable` into `@/hooks/use-controllable` (now backed by
`useLatestRef`) so other components can reuse Radix-style controllable
state without redefining it inline.

Replace the plain `<Input>` in the knowledge form's "Code language"
field with `<Autocomplete>` surfacing a curated list of 30 common
languages, while still accepting any free-text value the backend may
expect.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 23:10:45 +07:00
Sergey KozyrenkoandCursor 8da0312e9d feat(knowledge): allow docType edit, partial UPDATE, and field-clear on save
Backend now accepts `docType` on `UpdateKnowledgeDocumentInput`, so the
edit form was reworked end-to-end:

- Form schema mirrors REST length limits (content 65536, question 2048,
  description 1000, codeLang 100) — GraphQL itself doesn't enforce them.
- `docType` is editable on existing docs, not just on create. Switching
  it clears stale subtype values (`answerType`/`guideType`/`codeLang`)
  through `setValue` in the Select's `onValueChange`, not via effect, so
  freshly loaded documents keep their persisted subtype on first render.
- UPDATE is now a partial payload built from RHF's `dirtyFields`:
  untouched optional fields are omitted, cleared fields go out as `""`
  so the backend wipes them (previously `'' → undefined` silently
  swallowed clears, leaving stale values on the server).
- `KnowledgesProvider` drops the duplicate `Knowledge` interface and
  uses the GraphQL fragment directly; no more hand-rolled field mapping.
- Both submit paths (form button and "Save & Leave" dialog) parse
  values through `formSchema.safeParse` so trim/normalisation is
  identical regardless of code path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 11:36:11 +07:00
Sergey KozyrenkoandCursor 616b864b17 style: prettier reformat in unrelated utilities
No behavior change.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 09:49:22 +07:00
Sergey KozyrenkoandCursor 8b1cf00ba1 refactor(knowledge): split page into form, layout, header and field components
Extract the previously monolithic knowledge page into focused modules under
features/knowledges/ to match the resources/flows convention:

- knowledge-form: schema, helpers and RHF wiring
- knowledge-form-layout: desktop split / mobile stacked layouts
- knowledge-form-controls: meta fields and content (markdown) field
- knowledge-header / knowledge-layout: shared header and loading/not-found shell

Promote the unsaved-changes machinery to reusable primitives
(use-unsaved-changes-guard hook + unsaved-changes-dialog) so other forms
can adopt the same flow.

Tighten markdown-editor for knowledge content authoring: disable
transformPastedText (a leading "- " or "1. " no longer silently turns a
plain paste into a list/blockquote) and reserve the editor's bounding box
during tiptap initialization to avoid layout jumps on mount.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 09:49:22 +07:00
Sergey KozyrenkoandCursor 4a1f5ba527 fix(markdown-editor): keep RHF clean and undo stack empty on mount
On the knowledge edit page the Save button was enabled immediately
after load — and the toolbar's Undo button along with it — even though
the user had not edited anything. Two distinct mount-time noises were
leaking into RHF and the UndoRedo plugin.

1. tiptap-markdown re-serializes the initial content during view
   construction (whitespace around fenced code blocks, hard-break
   markers, etc.) and fires onUpdate with the normalized markdown.
   Forwarding this echo to field.onChange flips RHF's isDirty.
   Defer onUpdate forwarding via isInitializedRef set inside onCreate
   and use the editor's own serialized markdown as the echo baseline.

2. Construction-time transactions from trailingNode and the mount-time
   setContent land in the UndoRedo plugin's event stack. PM's
   history() uses a singleton PluginKey, so state.reconfigure({plugins})
   carries the old state over; reset the stack by rebuilding the entire
   EditorState instead, then dispatch an empty addToHistory:false
   transaction to wake tiptap-react's subscription so the toolbar
   re-renders. Only reset on initial mount and on external value sync
   so user edits are not erased.

Also dedupe knowledge.tsx Create/Update payload projection into a
shared formValuesToBasePayload helper and rename its parameter from
v to values per code-style conventions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 09:49:21 +07:00
Dmitry Ng ca1dfa138c feat(knowledge): enhance document update functionality with docType handling
- Introduced a new `docType` field in the `UpdateKnowledgeDocumentInput` to manage document type changes.
- Updated the `doUpdate` method to clear subtype fields (GuideType, AnswerType, CodeLang) when the document type changes.
- Added comprehensive tests for various document type transitions to ensure correct behavior and state preservation.
- Updated GraphQL schema and generated models to accommodate the new `docType` field.
2026-05-11 10:29:13 +03:00
Sergey KozyrenkoandCursor 4675cf8e15 feat(autocomplete): add free-text autocomplete primitive, wire to container path input
Build a small Autocomplete family on top of cmdk and Radix Popover so the
Pull dialog's container path field gets fuzzy-filtered suggestions from
paths the user has already touched (cached files, current listing,
ancestors), while still letting them type arbitrary paths. Renders inline
(no portal) to survive Radix Dialog's body scroll-lock, and delegates the
DOM input to the project Input component via Radix Slot (asChild) so cmdk
keeps the value sync and combobox ARIA without duplicating styles.

A single useCommandState selector returning a primitive hasActiveMatch
boolean keeps the per-keystroke re-render count down to one.

Drive-by: pluralize the "Upload file" dropdown item in FlowForm to "Upload
files" to match the input's multiple attribute.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 23:05:59 +07:00
Sergey KozyrenkoandCursor d97b355ce2 feat(file-manager): expand/collapse all toggle in header
Adds a chevron toggle next to the "Name" header that flips every
directory in the full tree (filter-independent). Uses the existing
overrides map via a new `setExpansion(paths, isExpanded)` bulk helper
on `useFileManagerExpansion`, so a single gesture wins over the
auto-expand-on-search behavior. Skeleton header stays in lock-step.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 21:25:42 +07:00
Sergey KozyrenkoandCursor 5fa3d65fe3 refactor(file-manager): replace mutating addAllToSet/removeAllFromSet with pure addAll/removeAll
The old helpers mutated the Set passed in, but the type signature
(`Set<string>`) did not communicate that — only the JSDoc did. Every
existing caller already cloned `prev` before calling, so the migration
to pure `(prev, paths) => new Set` variants is zero-cost and removes a
foot-gun. `toggleSubtreeOnSet` and `computeRowClickSelection` now
compose the pure helpers directly instead of clone-then-mutate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 20:52:15 +07:00
Sergey KozyrenkoandCursor 08fe658925 docs(knowledge): explain skipNextBlockRef cycle in KnowledgeForm
Document the ref-based loop between performSave, onSaveFromDialog, and
useUnsavedChangesGuard so a cold reader can answer "why is the ref
declared after performSave?" without re-tracing the whole component.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 20:49:37 +07:00
Sergey KozyrenkoandCursor 4055a981cb test(file-manager): pin compareSizes behavior with directories across folders-first toggle
Lock in the contract that directories collapse to size 0 in compareSizes.
Invisible while folders-first is on, but user-visible (and easy to regress)
once the Resources page lets the user disable that partition.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 20:49:05 +07:00
Sergey KozyrenkoandCursor 7125834e7e refactor(frontend): data-router migration, FileManager sorting, shared utilities
App shell:
- Switch from BrowserRouter+Routes to createBrowserRouter so pages can use
  data-router-only APIs (useBlocker). Route definitions live at module scope.

FileManager:
- Sortable headers (name/size/modified) with three-state cycle, optional
  per-column sortability, controlled / uncontrolled+localStorage modes via
  the new useFileManagerSorting hook.
- isFoldersFirst toggle and recursive sortFileManagerTree pure reducer.
- formatModifiedAbsolute alongside the relative formatter; hosts switch via
  the new Resources "Relative dates" toggle.
- Mirror hover highlight while a row's context/dropdown menu is open so the
  user knows which row the menu belongs to.
- Guard row pointerdown so a touch long-press no longer stacks the row menu
  on top of the empty-area menu.

Knowledge edit page:
- Decompose into focused subcomponents (header, intro, meta fields, content,
  desktop/mobile body, leave dialog).
- Targeted useWatch on docType so the markdown editor no longer re-renders
  meta fields on every keystroke.
- New useUnsavedChangesGuard hook encapsulates useBlocker, beforeunload and
  the "Save & Leave" dialog; supports skipNextBlock for post-save navigation.

MarkdownEditor:
- Suppress echo updates from tiptap-markdown's re-serializer so RHF's
  isDirty flag stays accurate.

Shared utilities:
- lib/local-storage.ts (zod-validated getStorageItem/setStorageItem) used by
  both table-storage.ts and the new file-manager-storage.ts.
- lib/table-sort.ts#cycleColumnSort shared between Knowledges and Templates.
- hooks/use-latest-ref.ts replaces five useRef+useEffect pairs in FileManager.

DataTable:
- Filter input upgraded to InputGroup with search icon and clear button.
- Extract DataTableFilter subcomponent to drop the inline IIFE.

Resources page:
- Per-page persisted view options (folders-first, relative dates, optional
  size/modified columns) extracted into a typed ResourcesViewOptions shape
  with a centralized defaultViewOptions and a single loadViewOptions reader.
- Promote upload / new-folder buttons to the page header.

Misc:
- Replace MoreHorizontal / MoreVertical icons with Ellipsis for visual
  consistency.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 20:36:54 +07:00
Dmitry Ng ba0cee43c8 fix(router): add new frontend routes for resources and knowledges to preserve route in the browser
- Introduced "/resources" and "/knowledges" routes to the frontendRoutes array in the router configuration, expanding the application's routing capabilities.
2026-05-08 20:46:47 +03:00
Dmitry Ng a9aeb81e4e refactor(resources): update MoveResourceRequest to allow empty destination
- Removed the requirement for the "destination" field in MoveResourceRequest, allowing it to be an empty string, which signifies moving to the root directory.
- Updated related documentation in swagger.json, swagger.yaml, and docs.go to reflect the new behavior.
- Adjusted the MoveResource function to handle cases where the destination is empty, ensuring proper path sanitization and resource movement semantics.
- Enhanced test cases to cover scenarios involving moving resources to the root directory and handling conflicts appropriately.
2026-05-08 20:24:39 +03:00
Dmitry Ng dc2fd43928 feat(assistants): support flowID=0 in REST API to create assistant with new flow
- Allow POST /flows/0/assistants/ to create a new flow together with the assistant, mirroring the existing GraphQL createAssistant(flowID: 0) behavior
- Require both assistants.create and flows.create permissions when flowID=0
- Add explicit flow ownership check for non-zero flowID using flows.admin scope
- Load flow data in the response by fetching it via assistant.FlowID after creation, ensuring AssistantFlow is fully populated in all cases
2026-05-08 18:54:14 +03:00
Sergey KozyrenkoandCursor 95893bf502 feat(knowledge): rich tiptap editor for content with per-panel scrolling
- replace plain textarea with a tiptap-based markdown editor (StarterKit, tiptap-markdown, placeholder) and a basic formatting toolbar so authors can edit knowledge content with rich formatting while keeping markdown as the storage format for embeddings
- introduce a reusable MarkdownEditor shared component and a tiptap Storage type augmentation
- move the save action from the input addon into the page header on the right
- give each desktop resizable panel its own internal scroll so long meta or content no longer makes the whole page scroll

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:55:38 +07:00
Sergey KozyrenkoandCursor 318acc9f77 feat(knowledge): add knowledge documents UI with list and detail pages
Wire up KnowledgesProvider with Apollo subscriptions and cache policies,
add /knowledges routes and sidebar entry, plus minor formatting cleanups
across file-manager, upload-validation and resources upload helpers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:55:38 +07:00
Sergey KozyrenkoandCursor 012cb741ad refactor(file-manager): chevron drill-in, responsive bulk bar and tri-state toggle fix
- File manager core:
  - Chevron click on folder rows now drills in via `onOpenDirectory`
    (when set), matching the existing double-click / Enter semantics.
    Consumers that don't wire the prop keep the legacy expand/collapse
    behaviour, so flow-files / resources trees stay unchanged.
  - Bulk actions bar wraps and collapses to icon-only buttons (Cancel
    included) on small viewports so the bar stays usable on mobile.
  - Fix tri-state subtree toggle requiring two clicks when the
    directory's own path was never in the selection (e.g. user ticked
    children one-by-one). \`toggleSubtreeOnSet\` now accepts an optional
    \`rootPath\` and ignores it for the "all selected?" check, mirroring
    what \`computeDirSelectionState\` shows on the visible checkbox.
    Covered by new regression tests in \`file-manager-utils.test.ts\`.
  - Built-in icons swapped: copy-path uses \`ClipboardCopy\` instead of
    \`Copy\`; bulk \"Save as resources\" uses \`FolderOutput\` instead of
    \`BookmarkPlus\`.

- Pull dialog: flatten the container listing (\`name\` as \`path\`,
  absolute path in \`id\`) so the chevron / double-click drill into
  real subfolders instead of toggling a synthetic \`work/\` wrapper that
  has no meaningful navigation target. Selection is mapped back to
  absolute paths via a name → absolute lookup before pulling.

- Resources page: drop the focus-derived \`currentDir\` plumbing —
  toolbar mkdir / upload always target the library root, row context
  menu loses \"Upload files here\" and renames \"New folder here\" to
  \"New folder\". Page wraps in \`h-[calc(100dvh-3rem)]\` so the bulk
  bar stays inside the viewport. Tooltips simplified accordingly.

- Flow files toolbar: replace the standalone Info icon with rich
  per-button tooltips that explain where each gesture lands
  (/work/uploads, /work/resources, separate Container snapshot area);
  upload button uses the standard \`Upload\` glyph.

- Attach resources dialog: switch the ad-hoc footer to a proper
  \`DialogFooter\` with responsive layout, tighten the dialog title.

- \`Dialog\` / \`Sheet\` footers always apply the inter-button gap,
  not only at the \`sm+\` breakpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:55:37 +07:00
Sergey KozyrenkoandCursor d743bf5105 refactor(flow-files): share upload validator and rely on subscriptions for cache sync
Two cleanups closing the remaining P2/P3 gaps in the flow-files / resources
upload + delete paths.

A.4 — unify upload-batch validation
- New shared validator at `frontend/src/lib/upload-validation.ts` (with
  tests) exposing `validateUploadBatch(files, limits)`. Mirrors the
  per-file / per-batch / empty-file rules enforced by both backends
  (`pkg/resources/resources.go`, `pkg/flowfiles/files.go`).
- `useResourcesUpload` migrated off its private validator onto the shared
  helper; behaviour and toast strings unchanged.
- `useFlowFilesUpload` now runs the same preflight before constructing
  the FormData. Hitting any of the 300 MB / 1000 files / 2 GB / 0-byte
  rules surfaces a synchronous toast instead of a network round-trip
  followed by a generic 413.
- Added `FLOW_FILES_MAX_FILE_SIZE_MB`, `FLOW_FILES_MAX_UPLOAD_TOTAL_SIZE_MB`
  and `FLOW_FILES_MAX_UPLOAD_FILES_PER_REQUEST` to flow-files-constants
  alongside the existing resources constants — kept as a separate set so
  the two backends can diverge later without touching unrelated call
  sites.

C.2 — drop redundant refetchFiles() after flow-file mutations
- The backend already publishes per-file flowFileAdded / flowFileDeleted
  events (with directory expansion on delete), `lib/apollo.ts` already
  maps them through the universal subscriptionCacheLink, and
  `useFlowFilesRealtime` already wires the three subscriptions. Only the
  imperative `await refetchFiles()` in upload/delete plus the
  `onSuccess={refetchFiles}` props on pull/attach dialogs were left over
  from the pre-subscription era.
- Dropped `refetchFiles` from `useFlowFilesUpload` and `useFlowFilesDelete`
  param types and removed the matching `await` calls. Added optional
  `onAfterDelete` to the delete hook (mirrors `useResourcesDelete`) for
  callers that want a UI hook without driving a refetch.
- Made `onSuccess` optional on `FlowFilesPullDialog` and
  `FlowFilesAttachResourcesDialog`; removed the `onSuccess={refetchFiles}`
  passthroughs in `flow-files.tsx`. The pull dialog's internal
  `refetchListing` over the container listing (a separate hook) is kept.

Verified: vitest 140/140, eslint clean on touched files, tsc shows only
the same pre-existing errors as before this change (`User.ts`/`user.ts`
casing; `single possibly undefined` in unrelated delete branches).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:55:37 +07:00
Sergey KozyrenkoandCursor 3540407aa6 feat(frontend): atomic batch resource ops and context-aware uploads
- Send `sources[]` (was single `source`) to /resources/copy,
  /resources/move and /files/to-resources so multi-select operations
  execute in one DB transaction; replace the per-feature 409 aggregation
  state and the now-unused `resources-conflict-dialog` with the shared
  `useOverwriteAction` workflow returning `OverwriteOutcome`.
- Extend `FileManager` with `emptyAreaActions` (right-click context menu
  over the tree's empty area), `appliesToFiles` filter (companion to
  `appliesToDirs`), `onActiveRowChange` focus reporting and row-level
  external-file drop (`onExternalFileDrop`).
- `useFilesDragAndDrop`: capture-phase `onDropCapture` resets the
  internal counter / `isDragging` flag before any descendant claims the
  drop with `stopPropagation`, fixing the page-level upload overlay
  staying stuck after a row-level drop.
- `useResourcesUpload` gains `defaultDir` (read via ref so `uploadFiles`
  stays reference-stable) and `openFilePickerForDir` so toolbar /
  sidebar / CTA pickers upload into the focused folder by default and
  per-row "Upload here" actions can target a specific directory.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:55:37 +07:00
Sergey KozyrenkoandCursor 76cb4c1283 refactor(frontend): unify overwrite flow and atomic multi-path file APIs
- Extract shared OverwriteConfirmDialog, OverwriteCtaButtons, and
  useOverwriteAction hook; reuse across Pull, Attach, Promote, Move, Copy
  dialogs to drop per-dialog overwrite switches in favour of explicit
  "… with overwrite" CTA + Replace-all confirm.
- Move file-manager into components/shared, add bulkDownloadAction and
  onOpenDirectory navigation override for navigation-style browsers.
- Switch flow files / resources delete and download to atomic multi-path
  endpoints via paths[]= query, drop Promise.allSettled fan-out.
- Add useFlowContainerFiles + container browse in Pull dialog with
  client-side conflict preflight (flow-files-conflicts).
- Extract getApiErrorStatusCode helper to deduplicate 409 detection.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:55:36 +07:00
Sergey KozyrenkoandCursor 0c65579066 feat(file-manager): extensible bulk actions API and additive shift-click selection
Replace the single `onBulkDelete` prop with a generic `bulkActions` array so the
host owns which gestures appear in the footer bar. Built-in helpers (`bulkDeleteAction`,
`bulkCopyPathsAction`, `bulkMoveAction`, `bulkCopyAction`, `bulkPromoteAction`)
match the row-action pattern; each entry supports inline button or trailing
overflow menu, optional confirm dialog, isDisabled / isHidden predicates, and
receives the deduped FileNode[]. The bar also surfaces a cumulative size
summary alongside the item count.

Fix Shift+click range selection so it matches user expectations and the rest
of the file-manager's folder semantics:

- Range clicks are now ADDITIVE: the visible-order slice is unioned onto the
  previous selection instead of replacing it, so Cmd-clicked picks, earlier
  shift-ranges, and explicitly clicked folder subtrees survive a follow-up
  Shift+click.
- Folders inside the slice expand to their full subtree via `dirSubtreePaths`,
  mirroring the contract of a plain folder click. Without this, a Shift+click
  across collapsed sibling folders left the folder paths in `selectedPaths`
  but rendered their tri-state checkboxes as unchecked because no descendants
  had been added to the selection.
- Anchor preservation across chained Shift+clicks is unchanged; toggle/single
  still move the anchor.

Migrate `flow-files`, `resources`, and the move/copy/promote dialogs to the
new `bulkActions` API.

Tests: 131 passing — covers reverse direction, expanded vs. collapsed folders,
nested folders, mixed file/folder anchors, empty folders in range, omitted
`dirSubtreePaths` (tree-less callers), anchor=null and off-screen anchor
fallbacks, chained shift-clicks accumulating subtrees, toggle + shift-click
combinations, idempotent same-row range clicks, and the additive contract's
explicit divergence from Finder/Explorer (range never shrinks the selection).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:55:36 +07:00
Dmitry Ng a348e41a31 feat(settings): add version and isDevelopMode fields to Settings model and GraphQL schema
- Introduced new fields `version` and `isDevelopMode` in the Settings model to provide application versioning and development mode status.
- Updated GraphQL schema and resolvers to support the new fields, ensuring they are accessible via the Settings query.
- Enhanced Swagger documentation to reflect the changes in the Settings API endpoint.
- Added necessary validation and response handling for the new fields in the Settings service.
2026-05-08 13:06:07 +03:00