@gntik-ai/templates 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { ActivityFeed, AppSidebarFooter, AppSidebarHeader, AppSidebarNav, AppTopbar, AuthHeader, BulkActionBar, ChartCard, CodePanel, CommentThread, ConfirmDestructive, CreateApiKeyDialog, DEPLOYMENT_COLUMNS, DEPLOYMENT_DETAILS, DEPLOYMENT_EVENTS, DEPLOYMENT_ROWS, DangerZone, DataTable, DateRangeFilter, DescriptionListCard, ErrorPanel, EvaluationScorecard, Faq, FeatureGrid, FileUploadPanel, FilterBar, FirstRunEmpty, FlowBuilder, ForgotPasswordForm, FormSection, Hero, InlineCallout, InviteMembersDialog, InvoiceTable, KPI_ITEMS, KpiRow, ListSkeleton, LogViewer, MembersTable, MfaChallenge, ModelKeysList, NoResultsEmpty, NotificationMatrix, PROJECT_ACTIVITY, PageHeader, PageToolbar, PaymentMethodCard, PendingInvitations, PlanCard, PricingTable, ProfileHeader, PromptEditor, QuotaMeters, RESOURCE_CARDS, ResetPasswordForm, ResourceCardGrid, SectionHeader, SessionsDevices, SettingsRow, SignInForm, SignUpForm, SsoButtons, StatusTimeline, StickyActionBar, SystemBanner, TokenCostCard, TraceWaterfall, TrendDelta, ValidationSummary, apiKeyExpiries, apiKeyScopes, appSidebarNavGroups, appSidebarUser, appSidebarUser as appSidebarUser$1, appSidebarWorkspaces, appTopbarNotifications, firstRunSteps, formatSpanDuration, generateDemoSecret, memberRoles, noAccessSample, notificationChannels, notificationDefaults, notificationEvents, sampleDeviceSessions, sampleInvitations, sampleInvoices, sampleMembers, samplePaymentMethod, samplePlan, samplePricingTiers, sampleProfile, sampleQuotas, ssoProviders, ssoProviders as ssoProviders$1 } from "@gntik-ai/blocks";
3
- import { Alert, AuthLayout, Avatar, AvatarGroup, Badge, Blockquote, Breadcrumbs, Button, CanvasLayout, Card, CardBody, CardDescription, CardHeader, CardTitle, Checkbox, CheckboxGroup, ClickableCard, CodeBlock, ContextMenu, ContextMenuContent, ContextMenuTrigger, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DocsLayout, Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, EmptyState, Field, FieldDescription, FieldError, FieldLabel, Grid, GridItem, HStack, Heading, IconButton, Input, InspectorLayout, InspectorToggle, JsonViewer, KanbanBoard, Kbd, KbdCombo, Link, ListInput, LiveAnnouncer, Logo, MegaMenu, MenuItem, MenuSeparator, Meter, MobileNav, NavList, OtpInput, OverflowList, Page, PasswordInput, PowerSearch, Progress, Section, Select, SelectContent, SelectItem, SelectTrigger, SelectableCard, SelectableCardGroup, Separator, SettingsLayout, SidebarLayout, SimpleSelect, Skeleton, Spinner, SplitLayout, Stack, StackedLayout, StatusDot, StatusLayout, StatusTag, Switch, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsList, TabsPanel, TabsTab, Text, Textarea, ThemeSwitcher, Thumbnail, ThumbnailList, Timer, Timestamp, Toggle, ToggleGroup, TokenInput, TooltipProvider, TransferList, TreeList, VisuallyHidden, WizardLayout, cn, emptyPowerSearchQuery, formatPowerSearch, matchPowerSearch, megaMenuNavItems, moveKanbanCard, parsePowerSearch, useAnnounce, useHasLiveAnnouncer, useI18n, useInspectorLayout, useTheme } from "@gntik-ai/ui";
3
+ import { Alert, AuthLayout, Avatar, AvatarGroup, Badge, Blockquote, Breadcrumbs, Button, CanvasLayout, Card, CardBody, CardDescription, CardHeader, CardTitle, Checkbox, CheckboxGroup, ClickableCard, CodeBlock, ContextMenu, ContextMenuContent, ContextMenuTrigger, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DocsLayout, Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, EmptyState, Field, FieldDescription, FieldError, FieldLabel, Grid, GridItem, HStack, Heading, IconButton, Input, InspectorLayout, InspectorToggle, JsonViewer, KanbanBoard, Kbd, KbdCombo, Link, ListInput, LiveAnnouncer, Logo, MegaMenu, MenuItem, MenuSeparator, Meter, MobileNav, NavList, OtpInput, OverflowList, Page, PasswordInput, PowerSearch, Progress, Section, Select, SelectContent, SelectItem, SelectTrigger, SelectableCard, SelectableCardGroup, Separator, SettingsLayout, SidebarLayout, SimpleSelect, Skeleton, Spinner, SplitLayout, Stack, StackedLayout, StatusDot, StatusLayout, StatusTag, Switch, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsList, TabsPanel, TabsTab, Text, Textarea, ThemeSwitcher, Thumbnail, ThumbnailList, Timer, Timestamp, Toggle, ToggleGroup, TokenInput, TooltipProvider, TransferList, TreeList, VisuallyHidden, WizardLayout, cn, emptyPowerSearchQuery, formatPowerSearch, matchPowerSearch, matchText, megaMenuNavItems, moveKanbanCard, parsePowerSearch, useAnnounce, useHasLiveAnnouncer, useI18n, useInspectorLayout, useTheme } from "@gntik-ai/ui";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { Fragment as Fragment$1, createElement, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
6
6
  import { ChatAnnouncer, ChatComposer, ChatLayout, ChatMessage, CitationList, Markdown, SuggestionChips, ToolCallCard } from "@gntik-ai/chat";
@@ -721,8 +721,8 @@ const DAY$1 = 864e5;
721
721
  const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
722
722
  const fieldValue$1 = (e, field) => field === "actor" ? e.actor.name : field === "resource" ? e.resource : field === "result" ? e.result : "";
723
723
  /** Applies the search text, the filter chips (OR within a field, AND across fields) and the date range. */
724
- function filterAuditEvents(events, query, filters, range) {
725
- const q = query.trim().toLowerCase();
724
+ function filterAuditEvents(events, query, filters, range, locale) {
725
+ const q = query.trim();
726
726
  const fields = [...new Set(filters.map((f) => f.field))];
727
727
  const from = range.start ? startOfDay(range.start).getTime() : -Infinity;
728
728
  const to = range.end ? startOfDay(range.end).getTime() + DAY$1 : Infinity;
@@ -732,13 +732,17 @@ function filterAuditEvents(events, query, filters, range) {
732
732
  e.actor.email,
733
733
  e.action,
734
734
  e.target
735
- ].some((v) => v.toLowerCase().includes(q))) return false;
735
+ ].some((v) => matchText(v, q, { locale }))) return false;
736
736
  if (!fields.every((field) => filters.some((f) => f.field === field && f.value === fieldValue$1(e, field)))) return false;
737
737
  const t = e.at.getTime();
738
738
  return t >= from && t < to;
739
739
  });
740
740
  }
741
- const csvCell = (v) => /[",\n]/.test(v) ? `"${v.replace(/"/g, "\"\"")}"` : v;
741
+ /** Neutralises leading spreadsheet formula triggers before applying CSV quoting. */
742
+ function escapeCsvCell(value) {
743
+ const safe = /^[=+@\t\r-]/.test(value) ? `'${value}` : value;
744
+ return /[",\n]/.test(safe) ? `"${safe.replace(/"/g, "\"\"")}"` : safe;
745
+ }
742
746
  /** CSV of the events (time, actor, action, resource, target, ip, result). */
743
747
  function auditEventsToCsv(events) {
744
748
  return [[
@@ -759,7 +763,7 @@ function auditEventsToCsv(events) {
759
763
  e.target,
760
764
  e.ip,
761
765
  e.result
762
- ])].map((r) => r.map(csvCell).join(",")).join("\n");
766
+ ])].map((r) => r.map(escapeCsvCell).join(",")).join("\n");
763
767
  }
764
768
  function downloadCsv(events) {
765
769
  if (typeof URL.createObjectURL !== "function") return;
@@ -772,6 +776,7 @@ function downloadCsv(events) {
772
776
  }
773
777
  /** Audit log: ConsoleShell + Page with FilterBar + DateRangeFilter, an events DataTable and a diff drawer. */
774
778
  function AuditLogPage(props) {
779
+ const { locale } = useI18n();
775
780
  const { events = auditEvents, filterFields = auditFilterFields, defaultRange, today, onExport = downloadCsv, breadcrumbs = auditBreadcrumbs, currentHref = "/activity", title = "Audit log", description = "Every change made in this workspace: who did it, when, from where and what changed.", shell } = props;
776
781
  const [query, setQuery] = useState("");
777
782
  const [filters, setFilters] = useState([]);
@@ -784,7 +789,7 @@ function AuditLogPage(props) {
784
789
  };
785
790
  });
786
791
  const [diffEvent, setDiffEvent] = useState(null);
787
- const rows = filterAuditEvents(events, query, filters, range);
792
+ const rows = filterAuditEvents(events, query, filters, range, locale);
788
793
  const columns = [
789
794
  {
790
795
  id: "at",
@@ -973,7 +978,7 @@ const meta$2 = {
973
978
  family: "Work",
974
979
  priority: "P2",
975
980
  status: "beta",
976
- description: "Workspace audit trail: FilterBar (search, actor / resource / result filters) and DateRangeFilter over a DataTable of events with actor, action, target and result; a drawer shows the change diff (CodeBlock); export to CSV.",
981
+ description: "Workspace audit trail: FilterBar (search, actor / resource / result filters) and DateRangeFilter over a DataTable of events with actor, action, target and result; a drawer shows the change diff (CodeBlock); export to CSV. CSV export neutralises leading spreadsheet formula triggers.",
977
982
  layout: "SidebarLayout · Page",
978
983
  blocks: [
979
984
  "app-sidebar",
@@ -3541,19 +3546,18 @@ const galleryColumns = [
3541
3546
  }
3542
3547
  ];
3543
3548
  /** Case-insensitive search over name and description. */
3544
- function searchResources(items, query) {
3545
- const q = query.trim().toLowerCase();
3546
- if (!q) return [...items];
3547
- return items.filter((i) => i.name.toLowerCase().includes(q) || (i.description ?? "").toLowerCase().includes(q));
3549
+ function searchResources(items, query, locale) {
3550
+ return items.filter((i) => matchText(i.name, query, { locale }) || matchText(i.description ?? "", query, { locale }));
3548
3551
  }
3549
3552
  //#endregion
3550
3553
  //#region src/resource-gallery/Page.tsx
3551
3554
  /** Resource gallery: toolbar with search and a grid/table view toggle over the same resources. */
3552
3555
  function ResourceGalleryPage({ title = "Resources", description = "Services, databases and jobs that make up this project.", breadcrumbs = galleryBreadcrumbs, items: initialItems = galleryItems, defaultView = "grid", onViewChange, createLabel = "New resource", onCreate, onAction, shell }) {
3556
+ const { locale } = useI18n();
3553
3557
  const [items, setItems] = useState(initialItems);
3554
3558
  const [view, setView] = useState(defaultView);
3555
3559
  const [query, setQuery] = useState("");
3556
- const visible = searchResources(items, query);
3560
+ const visible = searchResources(items, query, locale);
3557
3561
  const handleAction = (action, item) => {
3558
3562
  if (onAction) onAction(action, item);
3559
3563
  else if (action === "delete") setItems((prev) => prev.filter((i) => i.id !== item.id));
@@ -3728,16 +3732,17 @@ const text = (v) => typeof v === "string" || typeof v === "number" ? String(v).t
3728
3732
  * A filter field reads the column with the same id (its `accessor`), else the row property of that
3729
3733
  * name, so statuses come from the columns.
3730
3734
  */
3731
- function filterResources(rows, query, filters, columns) {
3732
- const q = query.trim().toLowerCase();
3735
+ function filterResources(rows, query, filters, columns, locale) {
3736
+ const q = query.trim();
3733
3737
  const byField = /* @__PURE__ */ new Map();
3734
3738
  for (const f of filters) byField.set(f.field, [...byField.get(f.field) ?? [], f.value.toLowerCase()]);
3735
3739
  return rows.filter((row) => {
3736
3740
  if (q) {
3737
3741
  const r = row;
3738
- let haystack = [r.name, r.id].map(text).filter((v) => v !== void 0);
3739
- if (haystack.length === 0 && columns) haystack = columns.map((c) => text(c.accessor?.(row))).filter((v) => v !== void 0);
3740
- if (!haystack.some((v) => v.includes(q))) return false;
3742
+ const searchText = (v) => typeof v === "string" || typeof v === "number" ? String(v) : void 0;
3743
+ let haystack = [r.name, r.id].map(searchText).filter((v) => v !== void 0);
3744
+ if (haystack.length === 0 && columns) haystack = columns.map((c) => searchText(c.accessor?.(row))).filter((v) => v !== void 0);
3745
+ if (!haystack.some((v) => matchText(v, q, { locale }))) return false;
3741
3746
  }
3742
3747
  for (const [field, values] of byField) {
3743
3748
  const value = text(fieldValue(row, field, columns));
@@ -3758,11 +3763,12 @@ const defaultArchive = (row) => "status" in row ? {
3758
3763
  } : row;
3759
3764
  /** Resource index: header, filter bar, selectable sortable table with pagination, bulk action bar and a no-results state. */
3760
3765
  function ResourceIndexPage({ title = "Deployments", description = "Every service deployed in this workspace, across regions.", breadcrumbs = resourceBreadcrumbs, rows: initialRows = resourceRows, columns = resourceColumns, filterFields = resourceFilterFields, savedViews = resourceSavedViews, getRowId = defaultRowId, getRowHref, onRowClick, linkColumnId, noun = ["deployment", "deployments"], createLabel = "New deployment", onCreate, onOpen, onBulkAction, archiveRow = defaultArchive, loading = false, shell }) {
3766
+ const { locale } = useI18n();
3761
3767
  const [rows, setRows] = useState(initialRows);
3762
3768
  const [query, setQuery] = useState("");
3763
3769
  const [filters, setFilters] = useState([]);
3764
3770
  const [selected, setSelected] = useState([]);
3765
- const visible = filterResources(rows, query, filters, columns);
3771
+ const visible = filterResources(rows, query, filters, columns, locale);
3766
3772
  const visibleSelected = selected.filter((id) => visible.some((r) => getRowId(r) === id));
3767
3773
  const tableColumns = useMemo(() => linkColumns(columns, linkColumnId ?? columns[0]?.id, getRowHref, onRowClick), [
3768
3774
  columns,
@@ -4048,19 +4054,18 @@ const EMPTY_SELECTION = {
4048
4054
  };
4049
4055
  const facetValue = (hit, facet) => facet === "type" ? hit.type : hit.owner;
4050
4056
  /** Hits whose title or snippet contain the query (case-insensitive). */
4051
- function matchQuery(hits, query) {
4052
- const q = query.trim().toLowerCase();
4053
- return q ? hits.filter((h) => `${h.title} ${h.snippet}`.toLowerCase().includes(q)) : [...hits];
4057
+ function matchQuery(hits, query, locale) {
4058
+ return hits.filter((h) => matchText(`${h.title} ${h.snippet}`, query, { locale }));
4054
4059
  }
4055
4060
  /** Search results: ConsoleShell + Page with Checkbox-group facets and results grouped by type. */
4056
4061
  function SearchResultsPage(props) {
4057
- const { t } = useI18n();
4062
+ const { t, locale } = useI18n();
4058
4063
  const { hits = searchHits, facets = searchFacets, defaultQuery = searchDefaultQuery, onQueryChange, onSelectionChange, breadcrumbs = searchBreadcrumbs, currentHref = "/search", shell } = props;
4059
4064
  const id = useId();
4060
4065
  const resultsRef = useRef(null);
4061
4066
  const [query, setQuery] = useState(defaultQuery);
4062
4067
  const [selection, setSelection] = useState(EMPTY_SELECTION);
4063
- const matched = matchQuery(hits, query);
4068
+ const matched = matchQuery(hits, query, locale);
4064
4069
  const results = matched.filter((h) => Object.keys(selection).every((f) => selection[f].length === 0 || selection[f].includes(facetValue(h, f))));
4065
4070
  const groups = searchTypeOrder.map((type) => ({
4066
4071
  type,
@@ -7158,12 +7163,12 @@ const usageSavedViews = [
7158
7163
  * Search by project name; filters match the row field named by the filter id (same field OR,
7159
7164
  * across fields AND). Filters on fields the row does not have are ignored.
7160
7165
  */
7161
- function filterUsage(rows, query, filters) {
7162
- const q = query.trim().toLowerCase();
7166
+ function filterUsage(rows, query, filters, locale) {
7167
+ const q = query.trim();
7163
7168
  const byField = /* @__PURE__ */ new Map();
7164
7169
  for (const f of filters) byField.set(f.field, [...byField.get(f.field) ?? [], f.value]);
7165
7170
  return rows.filter((row) => {
7166
- if (q && !row.project.toLowerCase().includes(q)) return false;
7171
+ if (q && !matchText(row.project, q, { locale })) return false;
7167
7172
  for (const [field, values] of byField) {
7168
7173
  const value = row[field];
7169
7174
  if (value !== void 0 && value !== null && !values.includes(String(value))) return false;
@@ -7286,6 +7291,7 @@ const TRAFFIC_CHART = {
7286
7291
  };
7287
7292
  /** Usage analytics: date range + filters, two charts and a sortable breakdown table with export. */
7288
7293
  function UsageAnalyticsPage({ title = "Usage analytics", description = "Traffic, errors and spend by project and environment.", breadcrumbs = usageBreadcrumbs, rows = usageRows, columns = usageColumns, filterFields = usageFilterFields, searchPlaceholder = "Search projects…", savedViews = usageSavedViews, primaryChart, trafficByRange: traffic = trafficByRange, trafficRanges: ranges = trafficRanges, secondaryChart, onRangeChange, onExport, shell }) {
7294
+ const { locale } = useI18n();
7289
7295
  const secondary = {
7290
7296
  ...TRAFFIC_CHART,
7291
7297
  ...secondaryChart
@@ -7293,7 +7299,7 @@ function UsageAnalyticsPage({ title = "Usage analytics", description = "Traffic,
7293
7299
  const [range, setRange] = useState(null);
7294
7300
  const [query, setQuery] = useState("");
7295
7301
  const [filters, setFilters] = useState([]);
7296
- const visible = filterUsage(rows, query, filters);
7302
+ const visible = filterUsage(rows, query, filters, locale);
7297
7303
  const clearFilters = () => setFilters([]);
7298
7304
  const exportAction = {
7299
7305
  label: "Export CSV",
@@ -12412,15 +12418,14 @@ const helpArticles = [
12412
12418
  }
12413
12419
  ];
12414
12420
  /** Articles whose title or excerpt contains the query. */
12415
- function searchArticles(articles, query) {
12416
- const q = query.trim().toLowerCase();
12417
- if (!q) return [...articles];
12418
- return articles.filter((a) => a.title.toLowerCase().includes(q) || a.excerpt.toLowerCase().includes(q));
12421
+ function searchArticles(articles, query, locale) {
12422
+ return articles.filter((a) => matchText(a.title, query, { locale }) || matchText(a.excerpt, query, { locale }));
12419
12423
  }
12420
12424
  //#endregion
12421
12425
  //#region src/help-home/Page.tsx
12422
12426
  /** Help center home on DocsLayout: search, category cards and popular articles. */
12423
12427
  function HelpHomePage(props) {
12428
+ const { locale } = useI18n();
12424
12429
  const { brand = /* @__PURE__ */ jsx(Logo, {
12425
12430
  size: 22,
12426
12431
  wordmark: true
@@ -12438,7 +12443,7 @@ function HelpHomePage(props) {
12438
12443
  }) } = props;
12439
12444
  const [query, setQuery] = useState("");
12440
12445
  const ids = useId();
12441
- const results = searchArticles(articles, query);
12446
+ const results = searchArticles(articles, query, locale);
12442
12447
  const searching = query.trim() !== "";
12443
12448
  const categoryTitle = (id) => categories.find((c) => c.id === id)?.title ?? id;
12444
12449
  const submit = (e) => {
@@ -13371,21 +13376,22 @@ const shortcutGroups = [
13371
13376
  }
13372
13377
  ];
13373
13378
  /** Groups with only the shortcuts whose description or keys match the query. */
13374
- function filterShortcuts(groups, query) {
13379
+ function filterShortcuts(groups, query, locale) {
13375
13380
  const q = query.trim().toLowerCase();
13376
13381
  if (!q) return [...groups];
13377
13382
  return groups.map((g) => ({
13378
13383
  ...g,
13379
- shortcuts: g.shortcuts.filter((s) => s.description.toLowerCase().includes(q) || g.title.toLowerCase().includes(q) || [...s.keys, ...s.then ?? []].join(" ").toLowerCase() === q)
13384
+ shortcuts: g.shortcuts.filter((s) => matchText(s.description, query, { locale }) || matchText(g.title, query, { locale }) || [...s.keys, ...s.then ?? []].join(" ").toLowerCase() === q)
13380
13385
  })).filter((g) => g.shortcuts.length > 0);
13381
13386
  }
13382
13387
  //#endregion
13383
13388
  //#region src/keyboard-shortcuts/ShortcutSheet.tsx
13384
13389
  /** Searchable shortcut sheet shared by the page and the dialog. */
13385
13390
  function ShortcutSheet({ groups, headingLevel = 2, columns = 2, autoFocus }) {
13391
+ const { locale } = useI18n();
13386
13392
  const [query, setQuery] = useState("");
13387
13393
  const countId = useId();
13388
- const visible = filterShortcuts(groups, query);
13394
+ const visible = filterShortcuts(groups, query, locale);
13389
13395
  const total = visible.reduce((n, g) => n + g.shortcuts.length, 0);
13390
13396
  return /* @__PURE__ */ jsxs(Stack, {
13391
13397
  gap: 5,
@@ -14508,14 +14514,14 @@ function triageFieldValue(i, field) {
14508
14514
  if (field === "owner") return i.owner;
14509
14515
  }
14510
14516
  /** True when the incident matches the search text and every filtered field (OR within a field). */
14511
- function matchesTriage(i, query, filters) {
14512
- const q = query.trim().toLowerCase();
14517
+ function matchesTriage(i, query, filters, locale) {
14518
+ const q = query.trim();
14513
14519
  if (q && ![
14514
14520
  i.id,
14515
14521
  i.title,
14516
14522
  i.service,
14517
14523
  i.owner
14518
- ].some((v) => v.toLowerCase().includes(q))) return false;
14524
+ ].some((v) => matchText(v, q, { locale }))) return false;
14519
14525
  const byField = /* @__PURE__ */ new Map();
14520
14526
  for (const f of filters) byField.set(f.field, [...byField.get(f.field) ?? [], f.value]);
14521
14527
  return [...byField].every(([field, values]) => values.includes(triageFieldValue(i, field) ?? ""));
@@ -14619,6 +14625,7 @@ function IncidentCard({ incident, now }) {
14619
14625
  });
14620
14626
  }
14621
14627
  function IncidentBoard({ title, description, defaultColumns, filterFields, defaultFilters, now, onMove, defaultOpenId, onDeclare, onExport }) {
14628
+ const { locale } = useI18n();
14622
14629
  const announce = useAnnounce();
14623
14630
  const [columns, setColumns] = useState(defaultColumns);
14624
14631
  const [query, setQuery] = useState("");
@@ -14626,7 +14633,7 @@ function IncidentBoard({ title, description, defaultColumns, filterFields, defau
14626
14633
  const [openId, setOpenId] = useState(defaultOpenId);
14627
14634
  const visible = columns.map((c) => ({
14628
14635
  ...c,
14629
- cards: c.cards.filter((i) => matchesTriage(i, query, filters))
14636
+ cards: c.cards.filter((i) => matchesTriage(i, query, filters, locale))
14630
14637
  }));
14631
14638
  const shown = visible.reduce((n, c) => n + c.cards.length, 0);
14632
14639
  const openColumn = columns.find((c) => c.cards.some((i) => i.id === openId));