@agent-native/core 0.114.11 → 0.114.13

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.
Files changed (109) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/application-state/index.ts +3 -0
  5. package/corpus/core/src/application-state/script-helpers.ts +12 -1
  6. package/corpus/core/src/application-state/store.ts +216 -15
  7. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +89 -1
  8. package/corpus/core/src/db/client.ts +44 -1
  9. package/corpus/templates/clips/changelog/2026-07-21-live-dictation-text-now-appears-above-the-desktop-recording-.md +6 -0
  10. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +14 -3
  11. package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +25 -0
  12. package/corpus/templates/clips/desktop/src/styles.css +20 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +3 -4
  14. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +11 -4
  15. package/corpus/templates/design/AGENTS.md +7 -3
  16. package/corpus/templates/design/actions/begin-node-rewrite-request.ts +78 -0
  17. package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +17 -7
  18. package/corpus/templates/design/actions/propose-node-rewrite.ts +26 -18
  19. package/corpus/templates/design/actions/resolve-node-rewrite.ts +67 -19
  20. package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +1 -4
  21. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +2 -6
  22. package/corpus/templates/design/shared/node-rewrite.ts +32 -0
  23. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +6 -3
  24. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +11 -18
  25. package/corpus/templates/forms/app/pages/FormsListPage.tsx +9 -5
  26. package/corpus/templates/slides/app/context/DeckContext.tsx +18 -0
  27. package/corpus/templates/slides/changelog/2026-07-21-decks-list-now-refreshes-when-you-switch-organizations.md +6 -0
  28. package/corpus/templates/tasks/app/components/custom-fields/FieldCreateBar.tsx +13 -19
  29. package/corpus/templates/tasks/app/components/custom-fields/FieldListRow.tsx +27 -22
  30. package/corpus/templates/tasks/app/components/custom-fields/FieldsList.tsx +26 -22
  31. package/corpus/templates/tasks/app/components/custom-fields/FieldsListSkeleton.tsx +4 -1
  32. package/corpus/templates/tasks/app/components/custom-fields/FieldsPage.tsx +4 -2
  33. package/corpus/templates/tasks/app/components/custom-fields/editor/FieldEditorSidebar.tsx +12 -3
  34. package/corpus/templates/tasks/app/components/custom-fields/editor/FieldTitleSection.tsx +6 -3
  35. package/corpus/templates/tasks/app/components/custom-fields/editor/config/CurrencyConfigControl.tsx +5 -2
  36. package/corpus/templates/tasks/app/components/custom-fields/editor/config/NumberConfigControl.tsx +5 -2
  37. package/corpus/templates/tasks/app/components/custom-fields/editor/config/PercentConfigControl.tsx +4 -1
  38. package/corpus/templates/tasks/app/components/custom-fields/editor/config/SelectConfigControl.tsx +17 -7
  39. package/corpus/templates/tasks/app/components/custom-fields/editor/config/select-colors.ts +37 -9
  40. package/corpus/templates/tasks/app/components/custom-fields/field-types.ts +23 -0
  41. package/corpus/templates/tasks/app/components/inbox/InboxList.tsx +24 -16
  42. package/corpus/templates/tasks/app/components/inbox/InboxListPage.tsx +5 -3
  43. package/corpus/templates/tasks/app/components/inbox/InboxListRow.tsx +7 -5
  44. package/corpus/templates/tasks/app/components/layout/Header.tsx +10 -10
  45. package/corpus/templates/tasks/app/components/layout/Layout.tsx +11 -7
  46. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +17 -10
  47. package/corpus/templates/tasks/app/components/shared/AddListItemInput.tsx +14 -8
  48. package/corpus/templates/tasks/app/components/shared/AlertDialog.tsx +6 -2
  49. package/corpus/templates/tasks/app/components/shared/BulkDeleteDialog.tsx +13 -6
  50. package/corpus/templates/tasks/app/components/shared/ChipSelect.tsx +12 -5
  51. package/corpus/templates/tasks/app/components/shared/DeleteItemDialog.tsx +6 -6
  52. package/corpus/templates/tasks/app/components/shared/ListViewHeader.tsx +3 -1
  53. package/corpus/templates/tasks/app/components/shared/SidePanel.tsx +5 -2
  54. package/corpus/templates/tasks/app/components/shared/list/ListRowDragHandle.tsx +3 -1
  55. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionBar.tsx +46 -20
  56. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionHeaderToggle.tsx +3 -1
  57. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionToolbar.tsx +10 -6
  58. package/corpus/templates/tasks/app/components/tasks/TaskList.tsx +24 -13
  59. package/corpus/templates/tasks/app/components/tasks/TaskListHeaderRow.tsx +4 -1
  60. package/corpus/templates/tasks/app/components/tasks/TaskListPage.tsx +5 -3
  61. package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +15 -6
  62. package/corpus/templates/tasks/app/components/tasks/fields/TaskFieldsSidebar.tsx +11 -6
  63. package/corpus/templates/tasks/app/components/tasks/fields/TaskTitleSection.tsx +8 -3
  64. package/corpus/templates/tasks/app/components/tasks/fields/controls/RichTextValueControl.tsx +12 -6
  65. package/corpus/templates/tasks/app/components/tasks/fields/controls/SingleSelectValueControl.tsx +6 -1
  66. package/corpus/templates/tasks/app/i18n/en-US.ts +209 -0
  67. package/corpus/templates/tasks/app/i18n/index.ts +34 -0
  68. package/corpus/templates/tasks/app/root.tsx +34 -2
  69. package/corpus/templates/tasks/app/routes/extensions.$id.tsx +2 -1
  70. package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -1
  71. package/corpus/templates/tasks/app/routes/fields.tsx +2 -1
  72. package/corpus/templates/tasks/app/routes/inbox.tsx +2 -1
  73. package/corpus/templates/tasks/app/routes/tasks.tsx +2 -1
  74. package/corpus/templates/tasks/app/routes/team.tsx +6 -3
  75. package/corpus/templates/tasks/changelog/2026-07-20-tasks-is-now-available-in-10-additional-languages.md +6 -0
  76. package/dist/application-state/index.d.ts +2 -2
  77. package/dist/application-state/index.d.ts.map +1 -1
  78. package/dist/application-state/index.js +2 -2
  79. package/dist/application-state/index.js.map +1 -1
  80. package/dist/application-state/script-helpers.d.ts +3 -1
  81. package/dist/application-state/script-helpers.d.ts.map +1 -1
  82. package/dist/application-state/script-helpers.js +7 -1
  83. package/dist/application-state/script-helpers.js.map +1 -1
  84. package/dist/application-state/store.d.ts +7 -1
  85. package/dist/application-state/store.d.ts.map +1 -1
  86. package/dist/application-state/store.js +143 -17
  87. package/dist/application-state/store.js.map +1 -1
  88. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  89. package/dist/client/extensions/EmbeddedExtension.js +65 -1
  90. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  91. package/dist/collab/awareness.d.ts +2 -2
  92. package/dist/collab/awareness.d.ts.map +1 -1
  93. package/dist/collab/routes.d.ts +1 -1
  94. package/dist/db/client.d.ts +7 -0
  95. package/dist/db/client.d.ts.map +1 -1
  96. package/dist/db/client.js +39 -1
  97. package/dist/db/client.js.map +1 -1
  98. package/dist/notifications/routes.d.ts +1 -1
  99. package/dist/observability/routes.d.ts +3 -3
  100. package/dist/progress/routes.d.ts +1 -1
  101. package/dist/secrets/routes.d.ts +6 -6
  102. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  103. package/dist/server/transcribe-voice.d.ts +1 -1
  104. package/package.json +1 -1
  105. package/src/application-state/index.ts +3 -0
  106. package/src/application-state/script-helpers.ts +12 -1
  107. package/src/application-state/store.ts +216 -15
  108. package/src/client/extensions/EmbeddedExtension.tsx +89 -1
  109. package/src/db/client.ts +44 -1
@@ -9,6 +9,7 @@ import {
9
9
  } from "@agent-native/core/client/collab";
10
10
  import { callAction } from "@agent-native/core/client/hooks";
11
11
  import { isEmbedAuthActive } from "@agent-native/core/client/host";
12
+ import { useOrg } from "@agent-native/core/client/org";
12
13
  import { nanoid } from "nanoid";
13
14
  import {
14
15
  createContext,
@@ -950,6 +951,7 @@ export const defaultSlideContent: Record<SlideLayout, string> = {
950
951
  };
951
952
 
952
953
  export function DeckProvider({ children }: { children: ReactNode }) {
954
+ const { data: org } = useOrg();
953
955
  const [decks, setDecks] = useState<Deck[]>([]);
954
956
  const [loading, setLoading] = useState(true);
955
957
  const [loadError, setLoadError] = useState(false);
@@ -1269,6 +1271,22 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1269
1271
  });
1270
1272
  }, [resetDeckBaseline]);
1271
1273
 
1274
+ // Switching orgs re-scopes list-decks server-side but leaves this context's
1275
+ // in-memory list untouched, so the previous org's decks linger. Reload when
1276
+ // the org id actually changes; skip the first observed id so we don't double
1277
+ // up on the mount fetch above.
1278
+ const lastOrgIdRef = useRef<string | null | undefined>(undefined);
1279
+ useEffect(() => {
1280
+ const orgId = org?.orgId ?? null;
1281
+ if (lastOrgIdRef.current === undefined) {
1282
+ lastOrgIdRef.current = orgId;
1283
+ return;
1284
+ }
1285
+ if (lastOrgIdRef.current === orgId) return;
1286
+ lastOrgIdRef.current = orgId;
1287
+ void reloadDecks();
1288
+ }, [org?.orgId, reloadDecks]);
1289
+
1272
1290
  // Fallback polling for deck list + open-deck changes. SSE is the primary
1273
1291
  // path; this catches agent/db writes that bypass it without hammering idle
1274
1292
  // editor pages.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-21
4
+ ---
5
+
6
+ Decks list now refreshes when you switch organizations
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { IconPlus } from "@tabler/icons-react";
2
3
  import { useState, type FormEvent } from "react";
3
4
 
@@ -17,17 +18,7 @@ import type { FieldConfig, FieldType } from "@/hooks/use-custom-fields";
17
18
  import { FieldConfigControl } from "./editor/config/FieldConfigControl";
18
19
  import { normalizedInitialConfig } from "./editor/config/utils";
19
20
  import type { FieldDraft } from "./editor/types";
20
-
21
- const FIELD_TYPE_OPTIONS: Array<{ value: FieldType; label: string }> = [
22
- { value: "text", label: "Text" },
23
- { value: "rich_text", label: "Rich text" },
24
- { value: "number", label: "Number" },
25
- { value: "percent", label: "Percent" },
26
- { value: "currency", label: "Currency" },
27
- { value: "single_select", label: "Single-select" },
28
- { value: "multi_select", label: "Multi-select" },
29
- { value: "date", label: "Date" },
30
- ];
21
+ import { FIELD_TYPE_LABEL_KEYS, FIELD_TYPE_VALUES } from "./field-types";
31
22
 
32
23
  const FIELD_TYPES_WITH_CONFIG = new Set<FieldType>([
33
24
  "currency",
@@ -44,6 +35,7 @@ export function FieldCreateBar({
44
35
  busy: boolean;
45
36
  onCreate: (draft: FieldDraft) => Promise<void>;
46
37
  }) {
38
+ const t = useT();
47
39
  const [title, setTitle] = useState("");
48
40
  const [type, setType] = useState<FieldType>("text");
49
41
  const [config, setConfig] = useState<FieldConfig>(
@@ -71,16 +63,18 @@ export function FieldCreateBar({
71
63
  onSubmit={(event) => void handleSubmit(event)}
72
64
  className="grid shrink-0 gap-3 rounded-lg border border-border bg-card p-3 md:grid-cols-[minmax(180px,1fr)_180px_auto] md:items-start"
73
65
  >
74
- <h2 className="text-sm font-medium md:col-span-3">Create new field</h2>
66
+ <h2 className="text-sm font-medium md:col-span-3">
67
+ {t("fields.createNewFieldHeading")}
68
+ </h2>
75
69
  <div className="grid gap-2">
76
70
  <Label htmlFor="new-field-title" className="sr-only">
77
- Field title
71
+ {t("fields.fieldTitleLabel")}
78
72
  </Label>
79
73
  <Input
80
74
  id="new-field-title"
81
75
  value={title}
82
76
  disabled={busy}
83
- placeholder="New field title"
77
+ placeholder={t("fields.newFieldTitlePlaceholder")}
84
78
  onChange={(event) => setTitle(event.currentTarget.value)}
85
79
  />
86
80
  </div>
@@ -89,14 +83,14 @@ export function FieldCreateBar({
89
83
  disabled={busy}
90
84
  onValueChange={(value) => handleTypeChange(value as FieldType)}
91
85
  >
92
- <SelectTrigger aria-label="Field type">
86
+ <SelectTrigger aria-label={t("fields.fieldTypeAriaLabel")}>
93
87
  <SelectValue />
94
88
  </SelectTrigger>
95
89
  <SelectContent>
96
90
  <SelectGroup>
97
- {FIELD_TYPE_OPTIONS.map((option) => (
98
- <SelectItem key={option.value} value={option.value}>
99
- {option.label}
91
+ {FIELD_TYPE_VALUES.map((value) => (
92
+ <SelectItem key={value} value={value}>
93
+ {t(FIELD_TYPE_LABEL_KEYS[value])}
100
94
  </SelectItem>
101
95
  ))}
102
96
  </SelectGroup>
@@ -104,7 +98,7 @@ export function FieldCreateBar({
104
98
  </Select>
105
99
  <Button type="submit" disabled={busy || title.trim().length === 0}>
106
100
  <IconPlus className="size-4" />
107
- Create
101
+ {t("fields.createButton")}
108
102
  </Button>
109
103
  {FIELD_TYPES_WITH_CONFIG.has(type) ? (
110
104
  <div className="md:col-span-3">
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { IconTrash } from "@tabler/icons-react";
2
3
 
3
4
  import type { SortableItemRenderProps } from "@/components/dnd/SortableItem";
@@ -7,47 +8,48 @@ import { Badge } from "@/components/ui/badge";
7
8
  import { Button } from "@/components/ui/button";
8
9
  import type { FieldDefinition, FieldType } from "@/hooks/use-custom-fields";
9
10
 
10
- const FIELD_TYPE_LABELS: Record<FieldType, string> = {
11
- text: "Text",
12
- rich_text: "Rich text",
13
- number: "Number",
14
- percent: "Percent",
15
- currency: "Currency",
16
- single_select: "Single-select",
17
- multi_select: "Multi-select",
18
- date: "Date",
19
- };
11
+ import { FIELD_TYPE_LABEL_KEYS } from "./field-types";
20
12
 
21
- function fieldTypeLabel(type: FieldType) {
22
- return FIELD_TYPE_LABELS[type] ?? type;
13
+ type Translate = ReturnType<typeof useT>;
14
+
15
+ function fieldTypeLabel(t: Translate, type: FieldType) {
16
+ const key = FIELD_TYPE_LABEL_KEYS[type];
17
+ return key ? t(key) : type;
23
18
  }
24
19
 
25
- function fieldDescription(field: FieldDefinition) {
26
- if (field.type === "currency") return `Currency ${field.config.symbol}`;
20
+ function fieldDescription(t: Translate, field: FieldDefinition) {
21
+ if (field.type === "currency") {
22
+ return t("fields.currencyDescription", { symbol: field.config.symbol });
23
+ }
27
24
  if (field.type === "number") {
28
25
  const parts: string[] = [];
29
- parts.push(`${field.config.precision ?? 0} decimals`);
30
- if (field.config.positiveOnly) parts.push("positive only");
26
+ parts.push(
27
+ t("fields.decimalsDescription", { count: field.config.precision ?? 0 }),
28
+ );
29
+ if (field.config.positiveOnly) parts.push(t("fields.positiveOnlySuffix"));
31
30
  return parts.join(" · ");
32
31
  }
33
32
  if (field.type === "percent") {
34
- return `${field.config.precision ?? 0} decimals`;
33
+ return t("fields.decimalsDescription", {
34
+ count: field.config.precision ?? 0,
35
+ });
35
36
  }
36
37
  if (field.type === "single_select" || field.type === "multi_select") {
37
38
  const options = "options" in field.config ? field.config.options : [];
38
- return `${options.length} options`;
39
+ return t("fields.optionsCountDescription", { count: options.length });
39
40
  }
40
- return fieldTypeLabel(field.type);
41
+ return fieldTypeLabel(t, field.type);
41
42
  }
42
43
 
43
44
  function FieldRowMetadata({ field }: { field: FieldDefinition }) {
45
+ const t = useT();
44
46
  return (
45
47
  <div className="flex min-w-0 flex-wrap items-center justify-end gap-1.5">
46
48
  <Badge variant="secondary" className="px-1.5 py-0 text-xs font-normal">
47
- {fieldTypeLabel(field.type)}
49
+ {fieldTypeLabel(t, field.type)}
48
50
  </Badge>
49
51
  <span className="truncate text-xs text-muted-foreground">
50
- {fieldDescription(field)}
52
+ {fieldDescription(t, field)}
51
53
  </span>
52
54
  </div>
53
55
  );
@@ -68,6 +70,7 @@ export function FieldListRow({
68
70
  onOpenDetails,
69
71
  onRequestDelete,
70
72
  }: FieldListRowProps) {
73
+ const t = useT();
71
74
  return (
72
75
  <ListRow
73
76
  sortable={sortable}
@@ -99,7 +102,9 @@ export function FieldListRow({
99
102
  type="button"
100
103
  variant="ghost"
101
104
  size="icon"
102
- aria-label={`Delete ${item.title}`}
105
+ aria-label={t("fields.deleteFieldAriaLabel", {
106
+ title: item.title,
107
+ })}
103
108
  onPointerDown={(event) => event.stopPropagation()}
104
109
  onClick={(event) => {
105
110
  event.stopPropagation();
@@ -1,4 +1,5 @@
1
1
  import { focusAgentChat } from "@agent-native/core/client/agent-chat";
2
+ import { useT } from "@agent-native/core/client/i18n";
2
3
  import { useCallback, useEffect, useMemo, useState } from "react";
3
4
  import { toast } from "sonner";
4
5
 
@@ -37,6 +38,7 @@ export function FieldsList({
37
38
  activeFieldId: string | null;
38
39
  setActiveFieldId: (fieldId: string | null) => void;
39
40
  }) {
41
+ const t = useT();
40
42
  const { fields, isPending, isError, error } = useCustomFields();
41
43
  const { fieldIds: visibleFieldIds } = useVisibleTaskFieldIds();
42
44
  const updateVisibleTaskFields = useUpdateVisibleTaskFields();
@@ -79,9 +81,9 @@ export function FieldsList({
79
81
  });
80
82
  setActiveFieldId(created.id);
81
83
  focusAgentChat();
82
- toast.success("Field created.");
84
+ toast.success(t("fields.createdToast"));
83
85
  } catch (caught) {
84
- toast.error((caught as Error)?.message ?? "Could not create field.");
86
+ toast.error((caught as Error)?.message ?? t("fields.createError"));
85
87
  throw caught;
86
88
  }
87
89
  }
@@ -94,10 +96,15 @@ export function FieldsList({
94
96
  setPendingDelete(null);
95
97
  if (activeFieldId === field.id) setActiveFieldId(null);
96
98
  toast.success(
97
- `Deleted ${field.title}${result.deletedValues ? ` and ${result.deletedValues} task values` : ""}.`,
99
+ result.deletedValues
100
+ ? t("fields.deletedWithValuesToast", {
101
+ title: field.title,
102
+ count: result.deletedValues,
103
+ })
104
+ : t("fields.deletedToast", { title: field.title }),
98
105
  );
99
106
  } catch (caught) {
100
- toast.error((caught as Error)?.message ?? "Could not delete field.");
107
+ toast.error((caught as Error)?.message ?? t("fields.deleteError"));
101
108
  }
102
109
  }
103
110
 
@@ -122,7 +129,7 @@ export function FieldsList({
122
129
  <div className="grid shrink-0 gap-3">
123
130
  <FieldCreateBar busy={busy} onCreate={handleCreate} />
124
131
  <ChipSelect
125
- label="Task card fields"
132
+ label={t("fields.taskCardFieldsLabel")}
126
133
  options={visibleFieldOptions}
127
134
  selectedIds={visibleFieldIds}
128
135
  onSelectedIdsChange={(fieldIds) =>
@@ -130,8 +137,8 @@ export function FieldsList({
130
137
  }
131
138
  disabled={busy || isPending || updateVisibleTaskFields.isPending}
132
139
  limit={TASK_CARD_FIELD_LIMIT}
133
- addButtonLabel="Add field"
134
- emptyLabel="No fields selected"
140
+ addButtonLabel={t("fields.addFieldButtonLabel")}
141
+ emptyLabel={t("fields.noFieldsSelectedLabel")}
135
142
  />
136
143
  </div>
137
144
 
@@ -140,22 +147,22 @@ export function FieldsList({
140
147
  <FieldsListSkeleton />
141
148
  ) : isError ? (
142
149
  <div
143
- aria-label="Fields list"
150
+ aria-label={t("fields.listAriaLabel")}
144
151
  className="min-h-0 flex-1 overflow-y-auto overscroll-contain pr-1 outline-none"
145
152
  >
146
153
  <ListErrorMessage
147
154
  error={error}
148
- fallbackMessage="Failed to load fields."
155
+ fallbackMessage={t("fields.loadError")}
149
156
  />
150
157
  </div>
151
158
  ) : orderedFields.length === 0 ? (
152
159
  <div
153
- aria-label="Fields list"
160
+ aria-label={t("fields.listAriaLabel")}
154
161
  className="min-h-0 flex-1 overflow-y-auto overscroll-contain pr-1 outline-none"
155
162
  >
156
163
  <ListEmptyState
157
- heading="No fields yet"
158
- description="Create a field above to start adding task structure."
164
+ heading={t("fields.emptyHeading")}
165
+ description={t("fields.emptyDescription")}
159
166
  />
160
167
  </div>
161
168
  ) : (
@@ -163,7 +170,7 @@ export function FieldsList({
163
170
  <List
164
171
  items={orderedFields}
165
172
  selectionEnabled={false}
166
- ariaLabel="Fields list"
173
+ ariaLabel={t("fields.listAriaLabel")}
167
174
  onReorder={handleReorder}
168
175
  renderItem={({ item, sortable }) => (
169
176
  <FieldListRow
@@ -198,18 +205,15 @@ export function FieldsList({
198
205
  onOpenChange={(open) => {
199
206
  if (!open) setPendingDelete(null);
200
207
  }}
201
- entityLabel="field"
208
+ entityLabel={t("fields.entitySingular")}
202
209
  itemTitle={pendingDelete?.title ?? null}
203
210
  pending={deleteField.isPending}
204
211
  description={
205
- pendingDelete ? (
206
- <>
207
- This deletes &quot;{pendingDelete.title}&quot; and removes its
208
- values from every task.
209
- </>
210
- ) : (
211
- "This deletes the field and removes its values from every task."
212
- )
212
+ pendingDelete
213
+ ? t("fields.deleteFieldDescriptionWithTitle", {
214
+ title: pendingDelete.title,
215
+ })
216
+ : t("fields.deleteFieldDescription")
213
217
  }
214
218
  onConfirm={() => void handleDelete()}
215
219
  />
@@ -1,11 +1,14 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+
1
3
  import { Skeleton } from "@/components/ui/skeleton";
2
4
 
3
5
  const SKELETON_ROW_COUNT = 3;
4
6
 
5
7
  export function FieldsListSkeleton() {
8
+ const t = useT();
6
9
  return (
7
10
  <div
8
- aria-label="Fields list"
11
+ aria-label={t("fields.listAriaLabel")}
9
12
  className="min-h-0 flex-1 overflow-y-auto overscroll-contain pr-1 outline-none"
10
13
  >
11
14
  <div className="grid gap-2">
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { useCallback } from "react";
2
3
  import { useSearchParams } from "react-router";
3
4
 
@@ -5,6 +6,7 @@ import { FieldsList } from "@/components/custom-fields/FieldsList";
5
6
  import { PageHeader } from "@/components/shared/PageHeader";
6
7
 
7
8
  export function FieldsPage() {
9
+ const t = useT();
8
10
  const [searchParams, setSearchParams] = useSearchParams();
9
11
  const activeFieldId = searchParams.get("field");
10
12
 
@@ -29,8 +31,8 @@ export function FieldsPage() {
29
31
  return (
30
32
  <div className="mx-auto flex h-full min-h-0 w-full max-w-5xl flex-col gap-6 overflow-hidden p-4 md:p-6">
31
33
  <PageHeader
32
- title="Fields"
33
- description="Define the reusable fields that every task can fill in."
34
+ title={t("fields.pageTitle")}
35
+ description={t("fields.pageDescription")}
34
36
  />
35
37
 
36
38
  <FieldsList
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { useCallback } from "react";
2
3
  import { toast } from "sonner";
3
4
 
@@ -19,10 +20,15 @@ export function FieldEditorSidebar({
19
20
  disabled: boolean;
20
21
  onClose: () => void;
21
22
  }) {
23
+ const t = useT();
22
24
  if (!field) return null;
23
25
 
24
26
  return (
25
- <SidePanel title="Field" closeLabel="Close field editor" onClose={onClose}>
27
+ <SidePanel
28
+ title={t("fieldEditor.panelTitle")}
29
+ closeLabel={t("fieldEditor.closeLabel")}
30
+ onClose={onClose}
31
+ >
26
32
  <FieldEditorSidebarPanel field={field} disabled={disabled} />
27
33
  </SidePanel>
28
34
  );
@@ -35,6 +41,7 @@ function FieldEditorSidebarPanel({
35
41
  field: FieldDefinition;
36
42
  disabled: boolean;
37
43
  }) {
44
+ const t = useT();
38
45
  const updateField = useUpdateCustomField();
39
46
 
40
47
  const saveUpdate = useCallback(
@@ -42,10 +49,12 @@ function FieldEditorSidebarPanel({
42
49
  void updateField
43
50
  .mutateAsync({ fieldId: field.id, ...payload })
44
51
  .catch((caught) => {
45
- toast.error((caught as Error)?.message ?? "Could not update field.");
52
+ toast.error(
53
+ (caught as Error)?.message ?? t("fieldEditor.updateError"),
54
+ );
46
55
  });
47
56
  },
48
- [field.id, updateField],
57
+ [field.id, updateField, t],
49
58
  );
50
59
 
51
60
  return (
@@ -1,3 +1,5 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+
1
3
  import { Input } from "@/components/ui/input";
2
4
  import { Label } from "@/components/ui/label";
3
5
 
@@ -10,21 +12,22 @@ export function FieldTitleSection({
10
12
  disabled?: boolean;
11
13
  onChange: (title: string) => void;
12
14
  }) {
15
+ const t = useT();
13
16
  return (
14
17
  <section className="grid gap-2 border-b border-border/70 px-3 py-3">
15
18
  <div className="flex min-w-0 items-center justify-between gap-2">
16
19
  <Label className="min-w-0 truncate text-[13px] font-medium">
17
- Title
20
+ {t("fieldEditor.titleLabel")}
18
21
  </Label>
19
22
  <span className="shrink-0 text-[11px] text-muted-foreground">
20
- Required
23
+ {t("fieldEditor.requiredLabel")}
21
24
  </span>
22
25
  </div>
23
26
  <Input
24
27
  value={title}
25
28
  disabled={disabled}
26
29
  onChange={(event) => onChange(event.currentTarget.value)}
27
- aria-label="Edit field title"
30
+ aria-label={t("fieldEditor.editFieldTitleAriaLabel")}
28
31
  className="h-9 text-[13px]"
29
32
  />
30
33
  </section>
@@ -1,3 +1,5 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+
1
3
  import { Input } from "@/components/ui/input";
2
4
  import { Label } from "@/components/ui/label";
3
5
 
@@ -12,10 +14,11 @@ export function CurrencyConfigControl({
12
14
  onChange,
13
15
  disabled = false,
14
16
  }: FieldConfigControlProps) {
17
+ const t = useT();
15
18
  return (
16
19
  <div className="grid gap-3 sm:grid-cols-[96px_1fr]">
17
20
  <div className="grid gap-2">
18
- <Label>Symbol</Label>
21
+ <Label>{t("fieldEditor.symbolLabel")}</Label>
19
22
  <Input
20
23
  maxLength={8}
21
24
  disabled={disabled}
@@ -29,7 +32,7 @@ export function CurrencyConfigControl({
29
32
  />
30
33
  </div>
31
34
  <div className="grid gap-2">
32
- <Label>Precision</Label>
35
+ <Label>{t("fieldEditor.precisionLabel")}</Label>
33
36
  <Input
34
37
  type="number"
35
38
  min={0}
@@ -1,3 +1,5 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+
1
3
  import { Checkbox } from "@/components/ui/checkbox";
2
4
  import { Input } from "@/components/ui/input";
3
5
  import { Label } from "@/components/ui/label";
@@ -13,10 +15,11 @@ export function NumberConfigControl({
13
15
  onChange,
14
16
  disabled = false,
15
17
  }: FieldConfigControlProps) {
18
+ const t = useT();
16
19
  return (
17
20
  <div className="grid gap-3">
18
21
  <div className="grid gap-2">
19
- <Label>Precision</Label>
22
+ <Label>{t("fieldEditor.precisionLabel")}</Label>
20
23
  <Input
21
24
  type="number"
22
25
  min={0}
@@ -44,7 +47,7 @@ export function NumberConfigControl({
44
47
  })
45
48
  }
46
49
  />
47
- Positive numbers only
50
+ {t("fieldEditor.positiveOnlyLabel")}
48
51
  </label>
49
52
  </div>
50
53
  </div>
@@ -1,3 +1,5 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+
1
3
  import { Input } from "@/components/ui/input";
2
4
  import { Label } from "@/components/ui/label";
3
5
 
@@ -8,9 +10,10 @@ export function PercentConfigControl({
8
10
  onChange,
9
11
  disabled = false,
10
12
  }: FieldConfigControlProps) {
13
+ const t = useT();
11
14
  return (
12
15
  <div className="grid gap-2">
13
- <Label>Precision</Label>
16
+ <Label>{t("fieldEditor.precisionLabel")}</Label>
14
17
  <Input
15
18
  type="number"
16
19
  min={0}
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { IconPlus, IconTrash } from "@tabler/icons-react";
2
3
 
3
4
  import { Button } from "@/components/ui/button";
@@ -26,6 +27,7 @@ export function SelectConfigControl({
26
27
  onChange,
27
28
  disabled = false,
28
29
  }: FieldConfigControlProps) {
30
+ const t = useT();
29
31
  const options = optionsFromConfig(config);
30
32
 
31
33
  function updateOption(
@@ -56,7 +58,7 @@ export function SelectConfigControl({
56
58
  ...options,
57
59
  {
58
60
  id: `opt_${crypto.randomUUID()}`,
59
- name: `Option ${nextIndex}`,
61
+ name: t("fieldEditor.newOptionName", { index: nextIndex }),
60
62
  color: "gray",
61
63
  sortOrder: options.length * 1000,
62
64
  },
@@ -66,7 +68,7 @@ export function SelectConfigControl({
66
68
 
67
69
  return (
68
70
  <div className="grid gap-2">
69
- <Label>Options</Label>
71
+ <Label>{t("fieldEditor.optionsLabel")}</Label>
70
72
  <div className="grid gap-2">
71
73
  {options.map((option, index) => (
72
74
  <div
@@ -79,7 +81,9 @@ export function SelectConfigControl({
79
81
  onChange={(event) =>
80
82
  updateOption(index, { name: event.currentTarget.value })
81
83
  }
82
- aria-label={`Option ${index + 1} name`}
84
+ aria-label={t("fieldEditor.optionNameAriaLabel", {
85
+ index: index + 1,
86
+ })}
83
87
  />
84
88
  <Select
85
89
  disabled={disabled}
@@ -88,7 +92,11 @@ export function SelectConfigControl({
88
92
  updateOption(index, { color: value as SelectColorToken })
89
93
  }
90
94
  >
91
- <SelectTrigger aria-label={`Option ${option.name} color`}>
95
+ <SelectTrigger
96
+ aria-label={t("fieldEditor.optionColorAriaLabel", {
97
+ name: option.name,
98
+ })}
99
+ >
92
100
  <SelectValue />
93
101
  </SelectTrigger>
94
102
  <SelectContent>
@@ -99,7 +107,7 @@ export function SelectConfigControl({
99
107
  <span
100
108
  className={cn("size-2 rounded-full", color.className)}
101
109
  />
102
- {color.label}
110
+ {t(color.labelKey)}
103
111
  </span>
104
112
  </SelectItem>
105
113
  ))}
@@ -112,7 +120,9 @@ export function SelectConfigControl({
112
120
  size="icon"
113
121
  disabled={disabled}
114
122
  onClick={() => removeOption(index)}
115
- aria-label={`Remove ${option.name}`}
123
+ aria-label={t("fieldEditor.removeOptionAriaLabel", {
124
+ name: option.name,
125
+ })}
116
126
  >
117
127
  <IconTrash className="size-4" />
118
128
  </Button>
@@ -127,7 +137,7 @@ export function SelectConfigControl({
127
137
  className="justify-self-start gap-2"
128
138
  >
129
139
  <IconPlus className="size-4" />
130
- Add option
140
+ {t("fieldEditor.addOptionButton")}
131
141
  </Button>
132
142
  </div>
133
143
  </div>