Switch the `provider` and `terminals` columns from `accessorKey` to
`accessorFn` so the global filter receives plain strings — the provider
name and the joined list of terminal images — instead of the raw object
or array, which the predicate would just stringify to `[object Object]`.
The cell renderers keep reading `row.original` directly, so the visible
output is unchanged.
Dates stay excluded: substring matching against formatted timestamps
gives unpredictable hits (e.g. typing "3" suddenly matches every row
whose ISO contains 03) and date filtering belongs in a range picker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Typing into the Flows search felt sluggish — Chrome reported INP 271 ms with input delay 253 ms, meaning every keystroke had to wait for the previous keystroke's render to complete before the event handler could even run. The chain on every keypress was: `setSearchParams` → react-router rerenders the whole route subtree → TanStack re-derives the global filter → 91 rows × 3 predicate calls → reconcile ~70 cells (Tooltip + Badge + ProviderIcon). At ~250 ms per cycle a fast typist queues several keystrokes behind a running render, so the input appeared to lag visibly.
Split the input value from the upstream commit: `DataTableFilter` now owns a local string state that updates synchronously on every keystroke, while a 150 ms debounce mirrors the value into `onQueryChange`. The router / TanStack cascade now fires once per typing pause instead of once per keystroke, and the input never has to wait its turn behind an in-flight reconciliation. After the change Chrome reports INP 43 ms with input delay 2 ms — typing feels instant.
Externally visible behaviour is unchanged: the X button clears immediately (no debounce on explicit clears), and external `query` resets (back button, programmatic clear) sync down through a guarded effect that ignores the value we just emitted ourselves. Vitest tests that previously asserted against the synchronous commit now wait for the debounced row-model update via `findBy*` / `waitFor`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`getColumnId` only has one consumer — `lib/` is for utilities shared across the codebase, not for a single-call helper. Move it next to `columnPickerLabel` as a module-level function inside `data-table.tsx`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous implementation drove `searchColumns` narrowing through a regenerated `columns` array with per-column `enableGlobalFilter`. It worked, but every selection change spawned a fresh set of `ColumnDef` objects via spread, invalidating TanStack's column-instance cache and conflicting with the upstream "memoise columns" guidance. The column-id resolver was also duplicated across two memos.
Replace it with TanStack's canonical mechanism for dynamic filters: pack the query and the active column set into a single `state.globalFilter` value of shape `{ columns, query }`, and consult both fields from a custom `globalFilterFn`. Any change to the query or to the active column set produces a new object reference, which is exactly what TanStack watches to re-run the filter pipeline — no imperative `setGlobalFilter` pokes, no closure-only narrowing that silently goes stale, and the parent's `ColumnDef` references reach `useReactTable` untouched so the column cache stays warm. The id resolver moves into a single `getColumnId` helper in `lib/column-utils.ts`.
A regression test exercises the exact sequence that exposed the bug fixed in d4c1b13 (type a query that matches via one column, then uncheck that column — the row must disappear without retyping). A second test pre-seeds `localStorage.searchColumns` with an id that the current columns no longer expose and asserts the new rebase effect prunes it on mount.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first implementation routed the active-column set through TanStack's
`getColumnCanGlobalFilter` predicate and tried to nudge the pipeline with
`table.setGlobalFilter((current) => current)`. TanStack treats that as a
no-op because the resolved value equals the current state, so the filter
pipeline never re-ran after the user narrowed the picker — the table kept
matching against the previous column set.
Bake the active set into per-column `enableGlobalFilter` via a memoised
`tanstackColumns` instead. TanStack sees a new columns reference whenever
the user's selection changes and refilters automatically, which is the
standard mechanism for this kind of dynamic filter and verified end-to-end
in the browser on the Flows list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mark `id`, `title`, and `status` as `meta.searchable: true` so the DataTable picks them up as candidates for the new global-filter search, and drop the explicit `filterColumn="title"` prop so users can search by uuid prefix or by typing a status keyword (e.g. "running"). `provider` and `terminals` stay excluded — both are non-string values that would only emit `[object Object]` matches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The single-column `filterColumn` prop forced every page to pick one searchable field, which is awkward when a row has several useful text fields (e.g. flows have both `title` and the original `task`). Switch the engine to TanStack's `globalFilter` so the input can match across an OR-set of columns, and add a "Search in" dropdown next to "Columns" that lets the user narrow that set at runtime.
Opt-in stays explicit: `filterColumn: string` keeps the legacy single-column behaviour (no picker), `string[]` activates the picker on the listed columns, and an undefined prop falls back to columns marked with `meta.searchable: true`. The chosen subset persists alongside other table preferences in `table_4_<path>`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Added a new SQL migration to insert toolcall privileges into the privileges table.
- Introduced the `ToolcallService` for managing toolcall data, including retrieval of toolcalls and flow-specific toolcalls.
- Implemented API endpoints for fetching toolcalls and toolcall details, with appropriate permission checks.
- Enhanced Swagger documentation to include new toolcall endpoints and their specifications.
- Created a new model for toolcalls, defining their structure and validation rules.
- Added error handling for invalid toolcall requests and not found scenarios.
- Included two new provider YAML files for Qwen 3.6 35B models: `vllm-qwen3.6-35b-a3b-fp8-no-think.provider.yml` and `vllm-qwen3.6-35b-a3b-fp8.provider.yml`.
- Updated Dockerfile to copy the new configuration files into the appropriate directory.
Detail pages duplicated DetailNavigationToolbar's internal navigation
state (prev/next, sheet open, position label) because mobile chrome lives
inside a DropdownMenuItem and could not reuse the toolbar component.
Promote useDetailNavigation to return a full DetailNavigationController,
have the leaf components (Buttons / Sheet / Toolbar) read from it, and
drop the ~40 LOC mobile mirror block on each of three pages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plain spans with `text-muted-foreground text-sm tabular-nums` rendered as
loose text floating in the header — visually weak and easy to miss. Wrap
each counter in a `<Badge variant="secondary">` so it reads as a discrete
pill (matches the rest of the design language, e.g. doc-type badges in
the knowledges list).
Touches three locations that all share the icon + title + counter
header pattern:
- `pages/templates/template.tsx`: Preset-templates mobile Sheet header
- `pages/templates/template.tsx`: Preset-templates desktop aside header
- `components/shared/detail-navigation/detail-navigation-sheet.tsx`:
detail-navigation sheet header (Flows / Templates / Knowledges count)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Preset-templates side panel on `/templates/:id` now mirrors the
header + scrollable body structure of `<DetailNavigationSheet>`, so the
two Sheet surfaces on this page (preset picker and Templates list) look
and behave the same: bordered header row with icon + title + total
counter on the right, scrollable body below.
- Mobile `<Sheet>` and desktop `<aside>` share an identical header
(`border-b p-4` with `FileText`, "Preset templates", and a
`tabular-nums` counter). The mobile variant uses `<SheetTitle>` so
screen readers announce it; the desktop variant uses an `<h3>` styled
to match. The previous `sr-only` SheetTitle is gone.
- Body switches from `<ScrollArea flex-1>` to a plain
`<div className="min-w-0 flex-1 overflow-y-auto">`. Radix `ScrollArea`'s
Viewport wraps children in a `display: table` div whose width grows to
intrinsic content size, defeating `w-full min-w-0` on the inner card
flex rows — the chevron buttons were rendering off-screen on a 390px
viewport because of it. Plain `overflow-y-auto` respects the parent
width and the chevrons land inside the Sheet.
- Each preset Card and its inner flex row pick up `w-full min-w-0`,
the title `<span>` picks up `min-w-0`, so the
`flex-1 min-w-0` title button actually shrinks and `truncate` kicks in
on long preset names. Asides also gain `gap-2` between cards so they
read as discrete items instead of a single bordered run.
- Drop the now-unused `ScrollArea` import.
- `components/ui/scroll-area.tsx`: collapse the `<Viewport>` JSX onto
one line and replace `h-full w-full` with `size-full` — a prettier
cleanup that landed alongside the rewrite.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both Sheets on the template detail page now follow the same skeleton:
SheetContent (flex flex-col gap-0 p-0)
SheetHeader (border-b p-4)
SheetTitle (flex items-center gap-2 pr-8 text-base)
<icon> <label> <counter on ml-auto>
ScrollArea (flex-1)
<list>
This mirrors `components/shared/detail-navigation/detail-navigation-sheet.tsx`,
so the navigation sheet (Templates 1/8 selector) and the Preset-templates
side panel look and behave the same way on every viewport — same width
cap (`max-w-sm`), same bordered header, same scroll behaviour via Radix
`ScrollArea` instead of plain `overflow-y-auto`.
The desktop `<aside>` variant gets the same skeleton too — wrapped in a
`flex flex-col` so the header strip stays pinned while the preset list
scrolls in the `ScrollArea` below. The pinned header swaps the muted
`<h3>` for the same icon + label + counter line as the mobile sheet, so
the platforms read the same.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Split the panel heading so each form factor gets the title styled for
its container:
- Mobile (`<Sheet>`): use a visible `<SheetTitle>` (no more `sr-only`)
in the sheet's own area, styled at `text-base` to match other Radix
dialog headers in the app.
- Desktop (inline `<aside>`): keep the existing muted `<h3>` outside
the scroll area so it stays pinned while the preset list scrolls.
Lift the title out of `asideContent` so it isn't rendered twice on
desktop; trim the wrapper's `p-4` to `px-4 pb-4` since the title
provides the top spacing in both layouts. Drop `p-2` on `SheetContent`
in favour of `p-0` so the new title can own its own padding.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Radix DialogContent (which `SheetContent` extends) warns when neither a
DialogTitle nor an explicit opt-out is provided, and again when no
Description / `aria-describedby={undefined}` is set. Two places hit
this:
- `pages/templates/template.tsx` preset-templates panel: the visible
`<h3>` lives inside `asideContent` and is reused by the desktop
non-Sheet variant, so add an `sr-only` `<SheetTitle>` inside
`<SheetContent>` for screen readers. Pair it with an explicit
`aria-describedby={undefined}` — the panel is a flat list of presets
with no descriptive sub-text, so opting out is honest.
- `components/shared/detail-navigation/detail-navigation-sheet.tsx`:
already had a `<SheetTitle>` (Flows / Templates / Knowledges), but the
Description warning was still firing. Add the same
`aria-describedby={undefined}` opt-out on `<SheetContent>` — the
listbox of items is self-describing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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.
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.
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.
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'.
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.
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`
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.
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).
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.
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.
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.
- @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>
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>
- 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>
- 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>
- 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>
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>
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>
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>
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>
- 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.
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.
- 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
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>
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>
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>
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>
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>
- 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.
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>