@parhelia/localization 0.1.12808 → 0.1.12838

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 (50) hide show
  1. package/dist/LocalizeItemCommand.d.ts.map +1 -1
  2. package/dist/LocalizeItemCommand.js +4 -2
  3. package/dist/LocalizeItemDialog.d.ts.map +1 -1
  4. package/dist/LocalizeItemDialog.js +7 -4
  5. package/dist/LocalizeItemUtils.d.ts.map +1 -1
  6. package/dist/LocalizeItemUtils.js +35 -25
  7. package/dist/api/discovery.d.ts.map +1 -1
  8. package/dist/api/discovery.js +20 -3
  9. package/dist/hooks/useTranslationWizard.d.ts.map +1 -1
  10. package/dist/hooks/useTranslationWizard.js +3 -3
  11. package/dist/services/translationService.d.ts +2 -2
  12. package/dist/services/translationService.d.ts.map +1 -1
  13. package/dist/services/translationService.js +21 -5
  14. package/dist/settings/TranslationServicesPanel.d.ts.map +1 -1
  15. package/dist/settings/TranslationServicesPanel.js +24 -21
  16. package/dist/setup/LocalizationSetupStep.d.ts.map +1 -1
  17. package/dist/setup/LocalizationSetupStep.js +29 -18
  18. package/dist/sidebar/TranslationSidebar.d.ts.map +1 -1
  19. package/dist/sidebar/TranslationSidebar.js +17 -10
  20. package/dist/steps/ItemSelectionStep.d.ts.map +1 -1
  21. package/dist/steps/ItemSelectionStep.js +2 -1
  22. package/dist/steps/ItemSelectionTree.d.ts.map +1 -1
  23. package/dist/steps/ItemSelectionTree.js +2 -1
  24. package/dist/steps/PromptCustomizationStep.d.ts.map +1 -1
  25. package/dist/steps/PromptCustomizationStep.js +6 -8
  26. package/dist/steps/ServiceLanguageSelectionStep.d.ts.map +1 -1
  27. package/dist/steps/ServiceLanguageSelectionStep.js +6 -4
  28. package/dist/steps/WizardStepShell.d.ts.map +1 -1
  29. package/dist/steps/types.d.ts.map +1 -1
  30. package/dist/translation-center/TranslationBatches.d.ts.map +1 -1
  31. package/dist/translation-center/TranslationBatches.js +187 -115
  32. package/dist/translation-center/TranslationManagement.js +4 -4
  33. package/dist/translation-center/TranslationsTitlebar.d.ts.map +1 -1
  34. package/dist/translation-center/TranslationsTitlebar.js +2 -2
  35. package/package.json +1 -1
  36. package/dist/constants.d.ts +0 -15
  37. package/dist/constants.d.ts.map +0 -1
  38. package/dist/constants.js +0 -21
  39. package/dist/steps/MetadataInputStep.d.ts +0 -4
  40. package/dist/steps/MetadataInputStep.d.ts.map +0 -1
  41. package/dist/steps/MetadataInputStep.js +0 -48
  42. package/dist/steps/SubitemDiscoveryStep.d.ts +0 -3
  43. package/dist/steps/SubitemDiscoveryStep.d.ts.map +0 -1
  44. package/dist/steps/SubitemDiscoveryStep.js +0 -313
  45. package/dist/steps/index.d.ts +0 -6
  46. package/dist/steps/index.d.ts.map +0 -1
  47. package/dist/steps/index.js +0 -5
  48. package/dist/utils/createVersions.d.ts +0 -14
  49. package/dist/utils/createVersions.d.ts.map +0 -1
  50. package/dist/utils/createVersions.js +0 -26
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef, useState, useMemo } from "react";
3
3
  import React from "react";
4
- import { Button, Select, Input, UserPicker, Popover, PopoverContent, PopoverTrigger, ContentTree, fetchItemStubs, searchUsers as searchBackendUsers, useEditContext, getLanguages, SimpleIconButton, } from "@parhelia/core";
4
+ import { Button, Select, Input, UserPicker, Popover, PopoverContent, PopoverTrigger, ContentTree, fetchItemStubs, searchUsers as searchBackendUsers, useEditContext, getLanguages, SimpleIconButton, DeleteIcon, } from "@parhelia/core";
5
5
  import { listBatches, getTranslationProviders, listBatchTranslationJobs, retryBatchTranslation, abortBatch, deleteBatch, } from "../services/translationService";
6
6
  import { useDebouncedCallback } from "use-debounce";
7
- import { X, ChevronDown, Languages, Loader2, Calendar, CheckCircle2, User as UserIcon2, Cloud, Globe, FileText, FolderTree, ExternalLink, Check, AlertCircle, CircleStop, Trash2, Hourglass, Copy, Sparkles, RotateCcw, } from "lucide-react";
7
+ import { X, ChevronDown, Languages, Loader2, Calendar, CheckCircle2, User as UserIcon2, Cloud, Globe, FileText, FolderTree, ExternalLink, Check, AlertCircle, CircleStop, Hourglass, Copy, Sparkles, RotateCcw, } from "lucide-react";
8
8
  import { toast } from "sonner";
9
9
  // Wrappers for lucide-react icons to work with React 19
10
10
  const XIcon = (props) => React.createElement(X, props);
@@ -22,7 +22,6 @@ const ExternalLinkIcon = (props) => React.createElement(ExternalLink, props);
22
22
  const CheckIcon = (props) => React.createElement(Check, props);
23
23
  const AlertCircleIcon = (props) => React.createElement(AlertCircle, props);
24
24
  const AbortIcon = (props) => React.createElement(CircleStop, props);
25
- const TrashIcon = (props) => React.createElement(Trash2, props);
26
25
  const QueuedIcon = (props) => React.createElement(Hourglass, props);
27
26
  const CopyIcon = (props) => React.createElement(Copy, props);
28
27
  const SparklesIcon = (props) => React.createElement(Sparkles, props);
@@ -73,7 +72,10 @@ function buildRetryJobRequest(job) {
73
72
  };
74
73
  }
75
74
  function normalizeGuid(value) {
76
- return (value ?? "").toString().replace(/[{}()]/g, "").toLowerCase();
75
+ return (value ?? "")
76
+ .toString()
77
+ .replace(/[{}()]/g, "")
78
+ .toLowerCase();
77
79
  }
78
80
  function dateRangeToFromUtc(range) {
79
81
  const now = new Date();
@@ -146,8 +148,10 @@ export function TranslationBatches() {
146
148
  });
147
149
  // Enforce current user filter for limited preview users
148
150
  useEffect(() => {
149
- if (isLimitedPreviewUser && currentUserName !== "all" && filters.user !== currentUserName) {
150
- setFilters(prev => ({ ...prev, user: currentUserName }));
151
+ if (isLimitedPreviewUser &&
152
+ currentUserName !== "all" &&
153
+ filters.user !== currentUserName) {
154
+ setFilters((prev) => ({ ...prev, user: currentUserName }));
151
155
  }
152
156
  }, [isLimitedPreviewUser, currentUserName, filters.user]);
153
157
  const effectiveFilters = useMemo(() => {
@@ -155,7 +159,9 @@ export function TranslationBatches() {
155
159
  const trimmedItem = filters.itemIdOrPath.trim();
156
160
  return {
157
161
  fromUtc: dateRangeToFromUtc(filters.dateRange),
158
- status: filters.status === "all" ? undefined : STATUS_FILTER_TO_DB[filters.status],
162
+ status: filters.status === "all"
163
+ ? undefined
164
+ : STATUS_FILTER_TO_DB[filters.status],
159
165
  user: userFilter === "all" ? undefined : userFilter,
160
166
  provider: filters.provider === "all" ? undefined : filters.provider,
161
167
  targetLanguage: filters.targetLanguage === "all" ? undefined : filters.targetLanguage,
@@ -178,9 +184,11 @@ export function TranslationBatches() {
178
184
  if (myRequestId !== requestIdRef.current)
179
185
  return;
180
186
  const maybeData = res?.data ?? res;
181
- const newBatches = Array.isArray(maybeData) ? maybeData : [];
187
+ const newBatches = Array.isArray(maybeData)
188
+ ? maybeData
189
+ : [];
182
190
  if (append) {
183
- setBatches(prev => {
191
+ setBatches((prev) => {
184
192
  const map = new Map();
185
193
  for (const b of prev)
186
194
  map.set(b.id, b);
@@ -222,7 +230,9 @@ export function TranslationBatches() {
222
230
  const loadProviders = async () => {
223
231
  try {
224
232
  const res = await getTranslationProviders();
225
- const providerData = (res?.data ?? res ?? []);
233
+ const providerData = (res?.data ??
234
+ res ??
235
+ []);
226
236
  setProviders(providerData);
227
237
  }
228
238
  catch (error) {
@@ -284,8 +294,7 @@ export function TranslationBatches() {
284
294
  await Promise.all(Array.from(unresolved).map(async (userName) => {
285
295
  try {
286
296
  const results = await searchBackendUsers(userName, 5);
287
- const exact = results.find((r) => r.userName?.toLowerCase() === userName.toLowerCase()) ??
288
- results[0];
297
+ const exact = results.find((r) => r.userName?.toLowerCase() === userName.toLowerCase()) ?? results[0];
289
298
  updates[userName] = exact?.displayName?.trim() || userName;
290
299
  }
291
300
  catch {
@@ -347,7 +356,7 @@ export function TranslationBatches() {
347
356
  // Languages available in the Target Language dropdown — sourced from Sitecore so
348
357
  // the list is stable regardless of the current filter selection.
349
358
  const languageOptions = useMemo(() => sitecoreLanguages
350
- .map(l => l.languageCode || l.name)
359
+ .map((l) => l.languageCode || l.name)
351
360
  .filter((code) => !!code)
352
361
  .sort(), [sitecoreLanguages]);
353
362
  // Group by date. Filtering happens server-side, so we operate on `batches` directly.
@@ -357,11 +366,14 @@ export function TranslationBatches() {
357
366
  const dedupMap = new Map();
358
367
  for (const b of batches)
359
368
  dedupMap.set(b.id, b);
360
- const sorted = Array.from(dedupMap.values()).sort((a, b) => new Date(b.startedAtUtc || b.createdAtUtc || b.lastUpdatedUtc || '').getTime() -
361
- new Date(a.startedAtUtc || a.createdAtUtc || a.lastUpdatedUtc || '').getTime());
362
- const list = sorted.map(b => ({
369
+ const sorted = Array.from(dedupMap.values()).sort((a, b) => new Date(b.startedAtUtc || b.createdAtUtc || b.lastUpdatedUtc || "").getTime() -
370
+ new Date(a.startedAtUtc || a.createdAtUtc || a.lastUpdatedUtc || "").getTime());
371
+ const list = sorted.map((b) => ({
363
372
  id: b.id,
364
- timestamp: b.startedAtUtc || b.createdAtUtc || b.lastUpdatedUtc || new Date().toISOString(),
373
+ timestamp: b.startedAtUtc ||
374
+ b.createdAtUtc ||
375
+ b.lastUpdatedUtc ||
376
+ new Date().toISOString(),
365
377
  info: b,
366
378
  }));
367
379
  const now = new Date();
@@ -396,16 +408,18 @@ export function TranslationBatches() {
396
408
  return dateGroups;
397
409
  }, [batches]);
398
410
  const loadBatchJobs = useCallback(async (batchId) => {
399
- setLoadingJobs(prev => {
411
+ setLoadingJobs((prev) => {
400
412
  const next = new Set(prev);
401
413
  next.add(batchId);
402
414
  return next;
403
415
  });
404
416
  try {
405
417
  const res = await listBatchTranslationJobs(batchId);
406
- const data = (res?.data ?? res ?? []);
418
+ const data = (res?.data ??
419
+ res ??
420
+ []);
407
421
  const jobs = Array.isArray(data) ? data : [];
408
- setBatchJobs(prev => ({ ...prev, [batchId]: jobs }));
422
+ setBatchJobs((prev) => ({ ...prev, [batchId]: jobs }));
409
423
  return jobs;
410
424
  }
411
425
  catch (error) {
@@ -413,7 +427,7 @@ export function TranslationBatches() {
413
427
  return [];
414
428
  }
415
429
  finally {
416
- setLoadingJobs(prev => {
430
+ setLoadingJobs((prev) => {
417
431
  const next = new Set(prev);
418
432
  next.delete(batchId);
419
433
  return next;
@@ -424,7 +438,7 @@ export function TranslationBatches() {
424
438
  loadBatchJobsRef.current = loadBatchJobs;
425
439
  }, [loadBatchJobs]);
426
440
  const toggleBatch = useCallback((batchId) => {
427
- setExpandedBatchId(prev => {
441
+ setExpandedBatchId((prev) => {
428
442
  const next = prev === batchId ? null : batchId;
429
443
  if (next && !batchJobs[next]) {
430
444
  void loadBatchJobs(next);
@@ -434,7 +448,7 @@ export function TranslationBatches() {
434
448
  setExpandedItems(new Set());
435
449
  }, [batchJobs, loadBatchJobs]);
436
450
  const toggleItem = useCallback((key) => {
437
- setExpandedItems(prev => {
451
+ setExpandedItems((prev) => {
438
452
  const next = new Set(prev);
439
453
  if (next.has(key))
440
454
  next.delete(key);
@@ -450,11 +464,13 @@ export function TranslationBatches() {
450
464
  acceptLabel: "Abort",
451
465
  showCancel: true,
452
466
  accept: async () => {
453
- setAbortingBatchIds(prev => new Set(prev).add(batchId));
467
+ setAbortingBatchIds((prev) => new Set(prev).add(batchId));
454
468
  try {
455
469
  const result = await abortBatch(batchId);
456
470
  if (result.type !== "success") {
457
- toast.error(result.details || result.summary || "Failed to abort translation batch.");
471
+ toast.error(result.details ||
472
+ result.summary ||
473
+ "Failed to abort translation batch.");
458
474
  return;
459
475
  }
460
476
  toast.success("Translation batch aborted.");
@@ -464,7 +480,7 @@ export function TranslationBatches() {
464
480
  }
465
481
  }
466
482
  finally {
467
- setAbortingBatchIds(prev => {
483
+ setAbortingBatchIds((prev) => {
468
484
  const next = new Set(prev);
469
485
  next.delete(batchId);
470
486
  return next;
@@ -472,7 +488,13 @@ export function TranslationBatches() {
472
488
  }
473
489
  },
474
490
  });
475
- }, [editContext, effectiveFilters, expandedBatchId, loadBatchJobs, loadRecentBatches]);
491
+ }, [
492
+ editContext,
493
+ effectiveFilters,
494
+ expandedBatchId,
495
+ loadBatchJobs,
496
+ loadRecentBatches,
497
+ ]);
476
498
  const handleDeleteBatch = useCallback((batchId) => {
477
499
  editContext?.confirm({
478
500
  header: "Delete Translation History",
@@ -480,24 +502,26 @@ export function TranslationBatches() {
480
502
  acceptLabel: "Delete",
481
503
  showCancel: true,
482
504
  accept: async () => {
483
- setDeletingBatchIds(prev => new Set(prev).add(batchId));
505
+ setDeletingBatchIds((prev) => new Set(prev).add(batchId));
484
506
  try {
485
507
  const result = await deleteBatch(batchId);
486
508
  if (result.type !== "success") {
487
- toast.error(result.details || result.summary || "Failed to delete translation batch.");
509
+ toast.error(result.details ||
510
+ result.summary ||
511
+ "Failed to delete translation batch.");
488
512
  return;
489
513
  }
490
514
  toast.success("Translation batch deleted.");
491
- setBatches(prev => prev.filter(batch => batch.id !== batchId));
492
- setBatchJobs(prev => {
515
+ setBatches((prev) => prev.filter((batch) => batch.id !== batchId));
516
+ setBatchJobs((prev) => {
493
517
  const next = { ...prev };
494
518
  delete next[batchId];
495
519
  return next;
496
520
  });
497
- setExpandedBatchId(prev => prev === batchId ? null : prev);
521
+ setExpandedBatchId((prev) => (prev === batchId ? null : prev));
498
522
  }
499
523
  finally {
500
- setDeletingBatchIds(prev => {
524
+ setDeletingBatchIds((prev) => {
501
525
  const next = new Set(prev);
502
526
  next.delete(batchId);
503
527
  return next;
@@ -520,7 +544,7 @@ export function TranslationBatches() {
520
544
  toast.error("No failed translations are available to retry.");
521
545
  return;
522
546
  }
523
- setRetryingKeys(prev => new Set(prev).add(retryKey));
547
+ setRetryingKeys((prev) => new Set(prev).add(retryKey));
524
548
  try {
525
549
  const result = await retryBatchTranslation({
526
550
  sessionId,
@@ -529,7 +553,9 @@ export function TranslationBatches() {
529
553
  jobs: retryJobs,
530
554
  });
531
555
  if (result.type !== "success") {
532
- toast.error(result.details || result.summary || "Failed to retry translations.");
556
+ toast.error(result.details ||
557
+ result.summary ||
558
+ "Failed to retry translations.");
533
559
  return;
534
560
  }
535
561
  const payload = (result?.data ?? result);
@@ -552,16 +578,23 @@ export function TranslationBatches() {
552
578
  }
553
579
  }
554
580
  finally {
555
- setRetryingKeys(prev => {
581
+ setRetryingKeys((prev) => {
556
582
  const next = new Set(prev);
557
583
  next.delete(retryKey);
558
584
  return next;
559
585
  });
560
586
  }
561
- }, [batchJobs, editContext?.sessionId, effectiveFilters, expandedBatchId, loadBatchJobs, loadRecentBatches]);
587
+ }, [
588
+ batchJobs,
589
+ editContext?.sessionId,
590
+ effectiveFilters,
591
+ expandedBatchId,
592
+ loadBatchJobs,
593
+ loadRecentBatches,
594
+ ]);
562
595
  const handleRetryBatch = useCallback(async (batch) => {
563
596
  const batchId = batch.id;
564
- const jobs = batchJobs[batchId] ?? await loadBatchJobs(batchId);
597
+ const jobs = batchJobs[batchId] ?? (await loadBatchJobs(batchId));
565
598
  await handleRetryJobs(batchId, batch.provider, jobs, `batch:${batchId}`);
566
599
  }, [batchJobs, handleRetryJobs, loadBatchJobs]);
567
600
  const openItemInEditor = useCallback(async (itemId, language) => {
@@ -577,8 +610,14 @@ export function TranslationBatches() {
577
610
  filters.provider !== "all" ||
578
611
  filters.targetLanguage !== "all" ||
579
612
  filters.itemIdOrPath.trim() !== "";
580
- const filterLabelClass = "flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-[0.06em] text-(--color-gray-2)";
581
- return (_jsxs("div", { className: "flex h-full flex-col min-h-0 bg-background", "data-testid": "translation-batches", children: [_jsx("div", { className: "shrink-0 px-4 pt-4 pb-3 md:px-6", children: _jsxs("div", { className: "flex flex-wrap items-end gap-x-3 gap-y-2", children: [_jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(CalendarIcon, { className: "h-3 w-3" }), "Date Range"] }), _jsx(Select, { className: "w-full bg-gray-5", options: DATE_RANGE_OPTIONS, value: filters.dateRange, onValueChange: (value) => setFilters((prev) => ({ ...prev, dateRange: value })), placeholder: "Select date range" })] }), _jsxs("div", { className: "flex min-w-[140px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(StatusIcon, { className: "h-3 w-3" }), "Status"] }), _jsx(Select, { className: "w-full bg-gray-5", options: STATUS_OPTIONS, value: filters.status, onValueChange: (value) => setFilters((prev) => ({ ...prev, status: value })), placeholder: "Select status" })] }), _jsxs("div", { className: "flex min-w-[200px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(UserFilterIcon, { className: "h-3 w-3" }), "User"] }), _jsx(UserPicker, { value: isLimitedPreviewUser
613
+ const filterLabelClass = "flex items-center gap-1.5 text-[10px] font-semibold tracking-[0.06em] text-neutral-grey-50";
614
+ return (_jsxs("div", { className: "flex h-full flex-col min-h-0 bg-background", "data-testid": "translation-batches", children: [_jsx("div", { className: "shrink-0 px-4 pt-4 pb-3 md:px-6", children: _jsxs("div", { className: "flex flex-wrap items-end gap-x-3 gap-y-2", children: [_jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(CalendarIcon, { className: "h-3 w-3" }), "Date Range"] }), _jsx(Select, { className: "w-full bg-neutral-grey-5", options: DATE_RANGE_OPTIONS, value: filters.dateRange, onValueChange: (value) => setFilters((prev) => ({
615
+ ...prev,
616
+ dateRange: value,
617
+ })), placeholder: "Select date range" })] }), _jsxs("div", { className: "flex min-w-[140px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(StatusIcon, { className: "h-3 w-3" }), "Status"] }), _jsx(Select, { className: "w-full bg-neutral-grey-5", options: STATUS_OPTIONS, value: filters.status, onValueChange: (value) => setFilters((prev) => ({
618
+ ...prev,
619
+ status: value,
620
+ })), placeholder: "Select status" })] }), _jsxs("div", { className: "flex min-w-[200px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(UserFilterIcon, { className: "h-3 w-3" }), "User"] }), _jsx(UserPicker, { value: isLimitedPreviewUser
582
621
  ? currentUserName
583
622
  : filters.user === "all"
584
623
  ? null
@@ -587,34 +626,44 @@ export function TranslationBatches() {
587
626
  : undefined, onChange: (user) => setFilters((prev) => ({
588
627
  ...prev,
589
628
  user: user?.userName ?? "all",
590
- })), disabled: isLimitedPreviewUser, placeholder: "All Users", buttonClassName: "h-[27px] w-full justify-start rounded-md border bg-gray-5 text-xs font-medium", allowClear: true, clearLabel: "All Users", searchUsers: searchTranslationUsers })] }), _jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(ProviderIcon, { className: "h-3 w-3" }), "Provider"] }), _jsx(Select, { className: "w-full bg-gray-5", options: [
629
+ })), disabled: isLimitedPreviewUser, placeholder: "All Users", buttonClassName: "h-[27px] w-full justify-start rounded-md border bg-neutral-grey-5 text-xs font-medium", allowClear: true, clearLabel: "All Users", searchUsers: searchTranslationUsers })] }), _jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(ProviderIcon, { className: "h-3 w-3" }), "Provider"] }), _jsx(Select, { className: "w-full bg-neutral-grey-5", options: [
591
630
  { value: "all", label: "All Providers" },
592
631
  ...providers.map((p) => ({
593
632
  value: p.name,
594
633
  label: p.displayName || p.name,
595
634
  })),
596
- ], value: filters.provider, onValueChange: (value) => setFilters((prev) => ({ ...prev, provider: value })), placeholder: "Select provider" })] }), _jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(GlobeIcon, { className: "h-3 w-3" }), "Target Language"] }), _jsx(Select, { className: "w-full bg-gray-5", options: [
635
+ ], value: filters.provider, onValueChange: (value) => setFilters((prev) => ({ ...prev, provider: value })), placeholder: "Select provider" })] }), _jsxs("div", { className: "flex min-w-[160px] flex-1 flex-col gap-1", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(GlobeIcon, { className: "h-3 w-3" }), "Target Language"] }), _jsx(Select, { className: "w-full bg-neutral-grey-5", options: [
597
636
  { value: "all", label: "All Languages" },
598
637
  ...languageOptions.map((lang) => ({
599
638
  value: lang,
600
639
  label: lang,
601
640
  })),
602
- ], value: filters.targetLanguage, onValueChange: (value) => setFilters((prev) => ({ ...prev, targetLanguage: value })), placeholder: "Select language" })] }), _jsxs("div", { className: "flex min-w-[200px] flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(ItemIcon, { className: "h-3 w-3" }), "Item ID / Path"] }), _jsxs("label", { className: "flex items-center gap-1 text-[10px] text-(--color-gray-2) cursor-pointer select-none", children: [_jsx("input", { type: "checkbox", checked: filters.itemIncludeSubitems, onChange: (e) => setFilters((prev) => ({ ...prev, itemIncludeSubitems: e.target.checked })), className: "h-3 w-3 accent-(--color-theme-secondary) cursor-pointer" }), "Include subitems"] })] }), _jsxs("div", { className: "relative", children: [_jsx(Input, { value: itemIdOrPathInput, onChange: (e) => {
641
+ ], value: filters.targetLanguage, onValueChange: (value) => setFilters((prev) => ({ ...prev, targetLanguage: value })), placeholder: "Select language" })] }), _jsxs("div", { className: "flex min-w-[200px] flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("label", { className: filterLabelClass, children: [_jsx(ItemIcon, { className: "h-3 w-3" }), "Item ID / Path"] }), _jsxs("label", { className: "flex items-center gap-1 text-[10px] text-neutral-grey-50 cursor-pointer select-none", children: [_jsx("input", { type: "checkbox", checked: filters.itemIncludeSubitems, onChange: (e) => setFilters((prev) => ({
642
+ ...prev,
643
+ itemIncludeSubitems: e.target.checked,
644
+ })), className: "h-3 w-3 accent-highlight-100 cursor-pointer" }), "Include subitems"] })] }), _jsxs("div", { className: "relative", children: [_jsx(Input, { value: itemIdOrPathInput, onChange: (e) => {
603
645
  const next = e.target.value;
604
646
  setItemIdOrPathInput(next);
605
647
  debouncedSetItemFilter(next);
606
- }, placeholder: "GUID or /sitecore/content/\u2026", className: "h-[27px] w-full bg-gray-5 text-xs pr-14" }), _jsxs("div", { className: "absolute right-1.5 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [itemIdOrPathInput && (_jsx("button", { type: "button", onClick: () => {
648
+ }, placeholder: "GUID or /sitecore/content/\u2026", className: "h-[27px] w-full bg-neutral-grey-5 text-xs pr-14" }), _jsxs("div", { className: "absolute right-1.5 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [itemIdOrPathInput && (_jsx("button", { type: "button", onClick: () => {
607
649
  setItemIdOrPathInput("");
608
650
  debouncedSetItemFilter.cancel();
609
- setFilters((prev) => ({ ...prev, itemIdOrPath: "", itemIncludeSubitems: false }));
610
- }, className: "text-gray-2 hover:text-(--color-dark) cursor-pointer", "aria-label": "Clear", children: _jsx(XIcon, { className: "h-3.5 w-3.5" }) })), _jsxs(Popover, { open: itemPickerOpen, onOpenChange: setItemPickerOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "text-gray-2 hover:text-(--color-dark) cursor-pointer", "aria-label": "Browse content tree", title: "Browse content tree", children: _jsx(FolderTreeIcon, { className: "h-3.5 w-3.5" }) }) }), _jsxs(PopoverContent, { align: "end", sideOffset: 6, className: "w-[28rem] max-w-[calc(100vw-2rem)] p-0", children: [_jsx("div", { className: "flex items-center justify-between gap-2 border-b border-gray-3 px-3 py-2 text-[11px] text-gray-2", children: _jsx("span", { children: "Pick item to filter by" }) }), _jsx("div", { className: "max-h-80 overflow-auto p-2", children: _jsx(ContentTree, { language: "en", selectionMode: "single", rootItemIds: ["0de95ae4-41ab-4d01-9eb0-67441b7c2450"], onSelectionChange: (items) => {
651
+ setFilters((prev) => ({
652
+ ...prev,
653
+ itemIdOrPath: "",
654
+ itemIncludeSubitems: false,
655
+ }));
656
+ }, className: "text-neutral-grey-50 hover:text-neutral-grey-100 cursor-pointer", "aria-label": "Clear", children: _jsx(XIcon, { className: "h-3.5 w-3.5" }) })), _jsxs(Popover, { open: itemPickerOpen, onOpenChange: setItemPickerOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "text-neutral-grey-50 hover:text-neutral-grey-100 cursor-pointer", "aria-label": "Browse content tree", title: "Browse content tree", children: _jsx(FolderTreeIcon, { className: "h-3.5 w-3.5" }) }) }), _jsxs(PopoverContent, { align: "end", sideOffset: 6, className: "w-[28rem] max-w-[calc(100vw-2rem)] p-0", children: [_jsx("div", { className: "flex items-center justify-between gap-2 border-b border-border-default px-3 py-2 text-[11px] text-neutral-grey-50", children: _jsx("span", { children: "Pick item to filter by" }) }), _jsx("div", { className: "max-h-80 overflow-auto p-2", children: _jsx(ContentTree, { language: "en", selectionMode: "single", rootItemIds: ["0de95ae4-41ab-4d01-9eb0-67441b7c2450"], onSelectionChange: (items) => {
611
657
  const picked = items?.[0];
612
658
  if (!picked)
613
659
  return;
614
660
  const applyValue = (value) => {
615
661
  setItemIdOrPathInput(value);
616
662
  debouncedSetItemFilter.cancel();
617
- setFilters((prev) => ({ ...prev, itemIdOrPath: value }));
663
+ setFilters((prev) => ({
664
+ ...prev,
665
+ itemIdOrPath: value,
666
+ }));
618
667
  setItemPickerOpen(false);
619
668
  };
620
669
  if (picked.path) {
@@ -623,13 +672,15 @@ export function TranslationBatches() {
623
672
  }
624
673
  // Fall back: resolve the item's path via a stub fetch
625
674
  // so the input shows a human-readable path instead of a GUID.
626
- fetchItemStubs([{ id: picked.id, language: "en", version: 1 }])
675
+ fetchItemStubs([
676
+ { id: picked.id, language: "en", version: 1 },
677
+ ])
627
678
  .then((stubs) => {
628
679
  const stubPath = stubs?.[0]?.path;
629
680
  applyValue(stubPath || picked.id);
630
681
  })
631
682
  .catch(() => applyValue(picked.id));
632
- } }) })] })] })] })] })] })] }) }), _jsx("div", { className: "flex-1 overflow-auto p-4 md:p-6 min-h-0", children: isLoading && batches.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "flex items-center gap-2 text-gray-2", children: [_jsx(LoaderIcon, { className: "h-5 w-5 animate-spin text-theme-secondary" }), "Loading recent translations..."] }) })) : groupedBatches.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-gray-2", children: [_jsx(LanguagesIcon, { className: "h-8 w-8 block mx-auto mb-2 text-gray-3" }), _jsx("p", { className: "font-medium text-(--color-gray-1)", children: "No translations found" }), _jsx("p", { className: "text-sm mt-1", children: (() => {
683
+ } }) })] })] })] })] })] })] }) }), _jsx("div", { className: "flex-1 overflow-auto p-4 md:p-6 min-h-0", children: isLoading && batches.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "flex items-center gap-2 text-neutral-grey-50", children: [_jsx(LoaderIcon, { className: "h-5 w-5 animate-spin text-highlight-100" }), "Loading recent translations..."] }) })) : groupedBatches.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-neutral-grey-50", children: [_jsx(LanguagesIcon, { className: "h-8 w-8 block mx-auto mb-2 text-neutral-grey-15" }), _jsx("p", { className: "font-medium text-neutral-grey-100", children: "No translations found" }), _jsx("p", { className: "text-sm mt-1", children: (() => {
633
684
  const hasActiveFilters = filters.dateRange !== "last30days" ||
634
685
  filters.status !== "all" ||
635
686
  filters.user !== currentUserName ||
@@ -639,45 +690,53 @@ export function TranslationBatches() {
639
690
  return hasActiveFilters
640
691
  ? "Try adjusting your filters or start a translation to see it appear here"
641
692
  : "Start a translation to see it appear here";
642
- })() })] }) })) : (_jsxs("div", { className: "space-y-10", children: [groupedBatches.map((dateGroup) => (_jsxs("div", { children: [_jsxs("div", { className: "flex items-baseline gap-2 mb-2", children: [_jsx("h2", { className: "text-[11px] font-semibold uppercase tracking-[0.08em] text-(--color-gray-2)", children: dateGroup.label }), _jsx("span", { className: "flex-1 ml-2 border-t border-gray-3" })] }), _jsx("div", { className: "divide-y divide-gray-3/70", children: dateGroup.batches.map((b) => {
693
+ })() })] }) })) : (_jsxs("div", { className: "space-y-10", children: [groupedBatches.map((dateGroup) => (_jsxs("div", { children: [_jsxs("div", { className: "flex items-baseline gap-2 mb-2", children: [_jsx("h2", { className: "text-[11px] font-semibold tracking-[0.08em] text-neutral-grey-50", children: dateGroup.label }), _jsx("span", { className: "flex-1 ml-2 border-t border-border-default" })] }), _jsx("div", { className: "divide-y divide-border-default/70", children: dateGroup.batches.map((b) => {
643
694
  const batchDate = new Date(b.timestamp);
644
695
  const now = new Date();
645
696
  const isToday = batchDate.toDateString() === now.toDateString();
646
697
  const timeDisplay = isToday
647
698
  ? batchDate.toLocaleTimeString()
648
- : batchDate.toLocaleDateString() + " " + batchDate.toLocaleTimeString();
699
+ : batchDate.toLocaleDateString() +
700
+ " " +
701
+ batchDate.toLocaleTimeString();
649
702
  const info = b.info;
650
703
  const totalJobs = info?.expectedJobs ?? 0;
651
- const itemsCount = typeof info?.itemsCount === 'number' ? info.itemsCount : undefined;
704
+ const itemsCount = typeof info?.itemsCount === "number"
705
+ ? info.itemsCount
706
+ : undefined;
652
707
  const languagesCsv = info?.languages;
653
- const languages = languagesCsv ? languagesCsv.split(',') : [];
708
+ const languages = languagesCsv
709
+ ? languagesCsv.split(",")
710
+ : [];
654
711
  const completedJobs = info?.completedJobs ?? 0;
655
712
  const errorJobs = info?.errorJobs ?? 0;
656
713
  const anyError = errorJobs > 0;
657
714
  const anyInProgress = isActiveBatchStatus(info?.status);
658
715
  const anyAborted = info?.status === "Aborted";
659
- const progressPct = totalJobs > 0 ? Math.min(100, Math.round((completedJobs / totalJobs) * 100)) : 0;
716
+ const progressPct = totalJobs > 0
717
+ ? Math.min(100, Math.round((completedJobs / totalJobs) * 100))
718
+ : 0;
660
719
  const showProgress = totalJobs > 0 && (anyInProgress || anyError);
661
720
  const statusDotClass = anyError
662
- ? "bg-red-400"
721
+ ? "bg-feedback-red"
663
722
  : anyInProgress
664
- ? "bg-(--color-theme-secondary)"
723
+ ? "bg-highlight-100"
665
724
  : anyAborted
666
- ? "bg-amber-400"
667
- : "bg-emerald-400/70";
725
+ ? "bg-feedback-orange"
726
+ : "bg-feedback-green/70";
668
727
  const metaParts = [];
669
- metaParts.push(_jsxs("span", { className: "text-(--color-gray-1)", children: [totalJobs, " translation", totalJobs !== 1 ? 's' : ''] }, "trans"));
670
- if (typeof itemsCount === 'number') {
671
- metaParts.push(_jsxs("span", { children: [itemsCount, " item", itemsCount !== 1 ? 's' : ''] }, "items"));
728
+ metaParts.push(_jsxs("span", { className: "text-neutral-grey-100", children: [totalJobs, " translation", totalJobs !== 1 ? "s" : ""] }, "trans"));
729
+ if (typeof itemsCount === "number") {
730
+ metaParts.push(_jsxs("span", { children: [itemsCount, " item", itemsCount !== 1 ? "s" : ""] }, "items"));
672
731
  }
673
732
  if (languages.length > 0) {
674
- metaParts.push(_jsxs("span", { title: languages.join(", "), children: [languages.length, " language", languages.length !== 1 ? 's' : ''] }, "langs"));
733
+ metaParts.push(_jsxs("span", { title: languages.join(", "), children: [languages.length, " language", languages.length !== 1 ? "s" : ""] }, "langs"));
675
734
  }
676
735
  if (info?.provider) {
677
736
  metaParts.push(_jsx("span", { children: info.provider }, "prov"));
678
737
  }
679
738
  if (info?.totalCost != null) {
680
- metaParts.push(_jsxs("span", { children: ["Cost: ", _jsx("span", { className: "font-medium text-(--color-gray-1)", children: formatUsdCost(info.totalCost) })] }, "cost"));
739
+ metaParts.push(_jsxs("span", { children: ["Cost:", " ", _jsx("span", { className: "font-medium text-neutral-grey-100", children: formatUsdCost(info.totalCost) })] }, "cost"));
681
740
  }
682
741
  if (info?.initiatedByUser) {
683
742
  const resolved = userDisplayNames[info.initiatedByUser];
@@ -701,12 +760,12 @@ export function TranslationBatches() {
701
760
  : anyAborted
702
761
  ? "aborted"
703
762
  : "completed";
704
- return (_jsxs("div", { "data-testid": `batch-row-${b.id}`, "data-batch-status": testStatus, children: [_jsx("div", { role: "button", tabIndex: 0, className: "group w-full text-left px-3 md:px-4 py-3 hover:bg-gray-5 transition-colors cursor-pointer", onClick: () => toggleBatch(b.id), onKeyDown: (e) => {
763
+ return (_jsxs("div", { "data-testid": `batch-row-${b.id}`, "data-batch-status": testStatus, children: [_jsx("div", { role: "button", tabIndex: 0, className: "group w-full text-left px-3 md:px-4 py-3 hover:bg-neutral-grey-5 transition-colors cursor-pointer", onClick: () => toggleBatch(b.id), onKeyDown: (e) => {
705
764
  if (e.key === "Enter" || e.key === " ") {
706
765
  e.preventDefault();
707
766
  toggleBatch(b.id);
708
767
  }
709
- }, children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [anyInProgress ? (_jsx(LoaderIcon, { className: "h-3 w-3 shrink-0 animate-spin text-theme-secondary" })) : (_jsx("span", { className: `h-1.5 w-1.5 shrink-0 rounded-full ${statusDotClass}`, "aria-hidden": "true" })), _jsx("span", { className: "text-(--color-dark-lighter) truncate", title: timeDisplay, children: (() => {
768
+ }, children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [anyInProgress ? (_jsx(LoaderIcon, { className: "h-3 w-3 shrink-0 animate-spin text-highlight-100" })) : (_jsx("span", { className: `h-1.5 w-1.5 shrink-0 rounded-full ${statusDotClass}`, "aria-hidden": "true" })), _jsx("span", { className: "text-neutral-grey-100 truncate", title: timeDisplay, children: (() => {
710
769
  let parsedName;
711
770
  if (info?.metadata) {
712
771
  try {
@@ -714,7 +773,8 @@ export function TranslationBatches() {
714
773
  ? JSON.parse(info.metadata)
715
774
  : info.metadata;
716
775
  const candidate = m?.name;
717
- if (typeof candidate === "string" && candidate.trim()) {
776
+ if (typeof candidate === "string" &&
777
+ candidate.trim()) {
718
778
  parsedName = candidate.trim();
719
779
  }
720
780
  }
@@ -722,18 +782,22 @@ export function TranslationBatches() {
722
782
  // Metadata isn't JSON — fall through to timestamp.
723
783
  }
724
784
  }
725
- return parsedName ?? `Translation Batch · ${timeDisplay}`;
726
- })() }), showProgress && (_jsxs("span", { className: `inline-flex items-center gap-1 text-[11px] tabular-nums ${anyError ? 'text-red-600' : 'text-theme-secondary'}`, "data-testid": `batch-progress-${b.id}`, children: [completedJobs, "/", totalJobs, anyError && _jsxs("span", { className: "text-red-600", children: ["\u00B7 ", errorJobs, " error", errorJobs !== 1 ? 's' : ''] })] }))] }), _jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[12px] text-gray-2 pl-4", children: [metaParts.map((part, i) => (_jsxs(React.Fragment, { children: [i > 0 && _jsx("span", { className: "text-gray-3", children: "\u00B7" }), part] }, i))), info?.lastUpdatedUtc && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-gray-3", children: "\u00B7" }), _jsx("span", { children: new Date(info.lastUpdatedUtc).toLocaleString() })] }))] })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [canRetry && (_jsx(SimpleIconButton, { onClick: (event) => {
785
+ return (parsedName ??
786
+ `Translation Batch · ${timeDisplay}`);
787
+ })() }), showProgress && (_jsxs("span", { className: `inline-flex items-center gap-1 text-[11px] tabular-nums ${anyError ? "text-feedback-red" : "text-highlight-100"}`, "data-testid": `batch-progress-${b.id}`, children: [completedJobs, "/", totalJobs, anyError && (_jsxs("span", { className: "text-feedback-red", children: ["\u00B7 ", errorJobs, " error", errorJobs !== 1 ? "s" : ""] }))] }))] }), _jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[12px] text-neutral-grey-50 pl-4", children: [metaParts.map((part, i) => (_jsxs(React.Fragment, { children: [i > 0 && (_jsx("span", { className: "text-neutral-grey-15", children: "\u00B7" })), part] }, i))), info?.lastUpdatedUtc && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-neutral-grey-15", children: "\u00B7" }), _jsx("span", { children: new Date(info.lastUpdatedUtc).toLocaleString() })] }))] })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [canRetry && (_jsx(SimpleIconButton, { onClick: (event) => {
727
788
  event.stopPropagation();
728
789
  void handleRetryBatch(info);
729
- }, icon: _jsx(RetryIcon, { className: `h-3.5 w-3.5 ${isRetryingBatch ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Retry failed translations", disabled: isRetryingBatch || isLoadingThis || isAborting || isDeleting, className: "p-0! text-red-600 hover:text-red-700" })), canAbort && (_jsx(SimpleIconButton, { onClick: (event) => {
790
+ }, icon: _jsx(RetryIcon, { className: `h-3.5 w-3.5 ${isRetryingBatch ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Retry failed translations", disabled: isRetryingBatch ||
791
+ isLoadingThis ||
792
+ isAborting ||
793
+ isDeleting, className: "p-0! text-feedback-red hover:text-feedback-red" })), canAbort && (_jsx(SimpleIconButton, { onClick: (event) => {
730
794
  event.stopPropagation();
731
795
  handleAbortBatch(b.id);
732
- }, icon: _jsx(AbortIcon, { className: `h-3.5 w-3.5 ${isAborting ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Abort translation batch", disabled: isAborting || isDeleting, className: "p-0! text-amber-600 hover:text-amber-700" })), canDelete && (_jsx(SimpleIconButton, { onClick: (event) => {
796
+ }, icon: _jsx(AbortIcon, { className: `h-3.5 w-3.5 ${isAborting ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Abort translation batch", disabled: isAborting || isDeleting, className: "p-0! text-feedback-orange hover:text-feedback-orange" })), canDelete && (_jsx(SimpleIconButton, { onClick: (event) => {
733
797
  event.stopPropagation();
734
798
  handleDeleteBatch(b.id);
735
- }, icon: _jsx(TrashIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), label: "Delete translation history", disabled: isDeleting || isAborting, className: "p-0! text-red-500 hover:text-red-600" })), _jsx("div", { className: `text-gray-3 transition-transform ${isExpanded ? 'rotate-180' : ''} group-hover:text-(--color-gray-2)`, children: _jsx(ChevronDownIcon, { className: "h-4 w-4" }) })] })] }) }), isExpanded && (_jsxs(_Fragment, { children: [_jsx(CustomPromptPanel, { prompts: parseCustomPrompts(info?.metadata) }), _jsx(BatchItemList, { batchId: b.id, batchProvider: info?.provider, jobs: jobs, isLoading: isLoadingThis, expandedItems: expandedItems, languages: sitecoreLanguages, itemFilter: filters.itemIdOrPath.trim(), itemIncludeSubitems: filters.itemIncludeSubitems, statusFilter: filters.status, batchIsTerminal: isTerminalBatchStatus(info?.status), batchStartedAtUtc: info?.startedAtUtc ?? info?.createdAtUtc, retryingKeys: retryingKeys, onToggleItem: toggleItem, onOpenItem: openItemInEditor, onRetryJobs: handleRetryJobs })] }))] }, b.id));
736
- }) })] }, dateGroup.label))), hasMore && (_jsx("div", { className: "flex justify-center pt-6", children: _jsx(Button, { variant: "outline", size: "sm", onClick: loadMore, disabled: isLoadingMore, children: isLoadingMore ? (_jsxs(_Fragment, { children: [_jsx(LoaderIcon, { className: "h-4 w-4 animate-spin text-theme-secondary" }), "Loading more..."] })) : (_jsxs(_Fragment, { children: [_jsx(ChevronDownIcon, { className: "h-4 w-4" }), "Load More Batches"] })) }) }))] })) })] }));
799
+ }, icon: _jsx(DeleteIcon, { size: "md" }), label: "Delete translation history", disabled: isDeleting || isAborting, className: "p-0! text-neutral-grey-50 hover:bg-neutral-grey-5 hover:text-neutral-grey-50" })), _jsx("div", { className: `text-neutral-grey-15 transition-transform ${isExpanded ? "rotate-180" : ""} group-hover:text-neutral-grey-50`, children: _jsx(ChevronDownIcon, { className: "h-4 w-4" }) })] })] }) }), isExpanded && (_jsxs(_Fragment, { children: [_jsx(CustomPromptPanel, { prompts: parseCustomPrompts(info?.metadata) }), _jsx(BatchItemList, { batchId: b.id, batchProvider: info?.provider, jobs: jobs, isLoading: isLoadingThis, expandedItems: expandedItems, languages: sitecoreLanguages, itemFilter: filters.itemIdOrPath.trim(), itemIncludeSubitems: filters.itemIncludeSubitems, statusFilter: filters.status, batchIsTerminal: isTerminalBatchStatus(info?.status), batchStartedAtUtc: info?.startedAtUtc ?? info?.createdAtUtc, retryingKeys: retryingKeys, onToggleItem: toggleItem, onOpenItem: openItemInEditor, onRetryJobs: handleRetryJobs })] }))] }, b.id));
800
+ }) })] }, dateGroup.label))), hasMore && (_jsx("div", { className: "flex justify-center pt-6", children: _jsx(Button, { variant: "outline", size: "sm", onClick: loadMore, disabled: isLoadingMore, children: isLoadingMore ? (_jsxs(_Fragment, { children: [_jsx(LoaderIcon, { className: "h-4 w-4 animate-spin text-highlight-100" }), "Loading more..."] })) : (_jsxs(_Fragment, { children: [_jsx(ChevronDownIcon, { className: "h-4 w-4" }), "Load More Batches"] })) }) }))] })) })] }));
737
801
  }
738
802
  function isLikelyGuid(value) {
739
803
  return /^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/i.test(value.trim());
@@ -785,7 +849,9 @@ function parseTranslationStatistics(metadata) {
785
849
  const fieldCount = Number(stats.fieldCount);
786
850
  const nonEmptyFieldCount = Number(stats.nonEmptyFieldCount ?? stats.translatedFieldCount ?? fieldCount);
787
851
  const emptyFieldCount = Number(stats.emptyFieldCount ?? Math.max(0, fieldCount - nonEmptyFieldCount));
788
- const sourceCharacterCount = Number(stats.sourceCharacterCount ?? stats.textCharacterCount ?? stats.characterCount);
852
+ const sourceCharacterCount = Number(stats.sourceCharacterCount ??
853
+ stats.textCharacterCount ??
854
+ stats.characterCount);
789
855
  if (!Number.isFinite(fieldCount) ||
790
856
  !Number.isFinite(nonEmptyFieldCount) ||
791
857
  !Number.isFinite(emptyFieldCount) ||
@@ -815,19 +881,19 @@ function formatUsdCost(value) {
815
881
  }).format(value);
816
882
  }
817
883
  function itemStatusDot(jobs) {
818
- const hasError = jobs.some(j => j.status === "Error");
819
- const hasInProgress = jobs.some(j => j.status === "In Progress");
820
- const hasPending = jobs.some(j => j.status === "Pending");
821
- const hasAborted = jobs.some(j => j.status === "Aborted");
884
+ const hasError = jobs.some((j) => j.status === "Error");
885
+ const hasInProgress = jobs.some((j) => j.status === "In Progress");
886
+ const hasPending = jobs.some((j) => j.status === "Pending");
887
+ const hasAborted = jobs.some((j) => j.status === "Aborted");
822
888
  if (hasError)
823
- return { cls: "bg-red-400", label: "Has errors" };
889
+ return { cls: "bg-feedback-red", label: "Has errors" };
824
890
  if (hasInProgress)
825
- return { cls: "bg-(--color-theme-secondary)", label: "In progress" };
891
+ return { cls: "bg-highlight-100", label: "In progress" };
826
892
  if (hasPending)
827
- return { cls: "bg-gray-3", label: "Queued" };
893
+ return { cls: "bg-neutral-grey-15", label: "Queued" };
828
894
  if (hasAborted)
829
- return { cls: "bg-amber-400", label: "Aborted" };
830
- return { cls: "bg-emerald-400/70", label: "Completed" };
895
+ return { cls: "bg-feedback-orange", label: "Aborted" };
896
+ return { cls: "bg-feedback-green/70", label: "Completed" };
831
897
  }
832
898
  function CustomPromptPanel({ prompts }) {
833
899
  const [openProvider, setOpenProvider] = React.useState(null);
@@ -853,21 +919,21 @@ function CustomPromptPanel({ prompts }) {
853
919
  toast.error("Failed to copy prompt to clipboard");
854
920
  }
855
921
  };
856
- return (_jsx("div", { className: "bg-gray-5/60 border-t border-gray-3/50", children: prompts.map((p) => {
922
+ return (_jsx("div", { className: "bg-neutral-grey-5/60 border-t border-border-default/50", children: prompts.map((p) => {
857
923
  const isOpen = openProvider === p.provider;
858
924
  const justCopied = copiedProvider === p.provider;
859
- return (_jsxs("div", { children: [_jsxs("div", { role: "button", tabIndex: 0, style: { paddingLeft: "2rem" }, className: "group flex items-center gap-2 pr-3 md:pr-4 py-2 text-[12px] text-gray-2 hover:bg-gray-5 transition-colors cursor-pointer", onClick: () => setOpenProvider(isOpen ? null : p.provider), onKeyDown: (e) => {
925
+ return (_jsxs("div", { children: [_jsxs("div", { role: "button", tabIndex: 0, style: { paddingLeft: "2rem" }, className: "group flex items-center gap-2 pr-3 md:pr-4 py-2 text-[12px] text-neutral-grey-50 hover:bg-neutral-grey-5 transition-colors cursor-pointer", onClick: () => setOpenProvider(isOpen ? null : p.provider), onKeyDown: (e) => {
860
926
  if (e.key === "Enter" || e.key === " ") {
861
927
  e.preventDefault();
862
928
  setOpenProvider(isOpen ? null : p.provider);
863
929
  }
864
- }, children: [_jsx(SparklesIcon, { className: "h-3 w-3 shrink-0 text-(--color-theme-secondary)", strokeWidth: 1.75 }), _jsx("span", { className: "text-(--color-dark-lighter)", children: "Custom prompt" }), _jsx("span", { className: "text-gray-3", children: "\u00B7" }), _jsx("span", { children: p.provider }), _jsxs("span", { className: "ml-auto flex items-center gap-2", children: [isOpen && (_jsx(SimpleIconButton, { onClick: (event) => {
930
+ }, children: [_jsx(SparklesIcon, { className: "h-3 w-3 shrink-0 text-highlight-100", strokeWidth: 1.75 }), _jsx("span", { className: "text-neutral-grey-100", children: "Custom prompt" }), _jsx("span", { className: "text-neutral-grey-15", children: "\u00B7" }), _jsx("span", { children: p.provider }), _jsxs("span", { className: "ml-auto flex items-center gap-2", children: [isOpen && (_jsx(SimpleIconButton, { onClick: (event) => {
865
931
  event.stopPropagation();
866
932
  void handleCopy(p.provider, p.prompt);
867
- }, icon: justCopied ? (_jsx(CheckIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 })) : (_jsx(CopyIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 })), label: justCopied ? "Copied" : "Copy prompt", className: "p-0! text-gray-2 hover:text-(--color-dark-lighter)" })), _jsx(ChevronDownIcon, { className: `h-3.5 w-3.5 text-gray-3 transition-transform group-hover:text-(--color-gray-2) ${isOpen ? "rotate-180" : ""}` })] })] }), isOpen && (_jsx("div", { style: { paddingLeft: "2.5rem" }, className: "pr-3 md:pr-4 pb-3", children: _jsx("pre", { className: "max-h-64 overflow-y-auto whitespace-pre-wrap break-words rounded border border-gray-3/50 bg-white p-2 font-mono text-[12px] text-(--color-dark-lighter)", children: p.prompt }) }))] }, p.provider));
933
+ }, icon: justCopied ? (_jsx(CheckIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 })) : (_jsx(CopyIcon, { className: "h-3.5 w-3.5", strokeWidth: 1.5 })), label: justCopied ? "Copied" : "Copy prompt", className: "p-0! text-neutral-grey-50 hover:text-neutral-grey-100" })), _jsx(ChevronDownIcon, { className: `h-3.5 w-3.5 text-neutral-grey-15 transition-transform group-hover:text-neutral-grey-50 ${isOpen ? "rotate-180" : ""}` })] })] }), isOpen && (_jsx("div", { style: { paddingLeft: "2.5rem" }, className: "pr-3 md:pr-4 pb-3", children: _jsx("pre", { className: "max-h-64 overflow-y-auto whitespace-pre-wrap break-words rounded border border-border-default/50 bg-white p-2 font-mono text-[12px] text-neutral-grey-100", children: p.prompt }) }))] }, p.provider));
868
934
  }) }));
869
935
  }
870
- function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems, languages, itemFilter, itemIncludeSubitems, statusFilter, batchIsTerminal, batchStartedAtUtc, retryingKeys, onToggleItem, onOpenItem, onRetryJobs }) {
936
+ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems, languages, itemFilter, itemIncludeSubitems, statusFilter, batchIsTerminal, batchStartedAtUtc, retryingKeys, onToggleItem, onOpenItem, onRetryJobs, }) {
871
937
  const languageMap = React.useMemo(() => {
872
938
  const map = new Map();
873
939
  for (const l of languages) {
@@ -907,11 +973,11 @@ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems,
907
973
  if (isLikelyGuid(filter)) {
908
974
  const normalized = normalizeGuid(filter);
909
975
  if (itemIncludeSubitems) {
910
- const rootJob = effectiveJobs.find(j => normalizeGuid((j.itemId || "").toString()) === normalized);
976
+ const rootJob = effectiveJobs.find((j) => normalizeGuid((j.itemId || "").toString()) === normalized);
911
977
  pathPrefix = rootJob?.itemPath ? rootJob.itemPath.toLowerCase() : null;
912
978
  }
913
979
  if (!pathPrefix) {
914
- return effectiveJobs.filter(j => normalizeGuid((j.itemId || "").toString()) === normalized);
980
+ return effectiveJobs.filter((j) => normalizeGuid((j.itemId || "").toString()) === normalized);
915
981
  }
916
982
  }
917
983
  else if (filter.startsWith("/")) {
@@ -923,12 +989,12 @@ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems,
923
989
  if (itemIncludeSubitems) {
924
990
  const prefix = pathPrefix;
925
991
  const prefixSlash = prefix.endsWith("/") ? prefix : prefix + "/";
926
- return effectiveJobs.filter(j => {
992
+ return effectiveJobs.filter((j) => {
927
993
  const p = (j.itemPath || "").toLowerCase();
928
994
  return p === prefix || p.startsWith(prefixSlash);
929
995
  });
930
996
  }
931
- return effectiveJobs.filter(j => (j.itemPath || "").toLowerCase() === pathPrefix);
997
+ return effectiveJobs.filter((j) => (j.itemPath || "").toLowerCase() === pathPrefix);
932
998
  }, [effectiveJobs, itemFilter, itemIncludeSubitems]);
933
999
  // Keep all jobs of items that have at least one job matching the selected status —
934
1000
  // this way the per-item language overview stays intact even when filtering.
@@ -946,15 +1012,15 @@ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems,
946
1012
  }
947
1013
  if (matchingItemIds.size === 0)
948
1014
  return [];
949
- return filteredJobs.filter(j => matchingItemIds.has((j.itemId || "").toString().toLowerCase()));
1015
+ return filteredJobs.filter((j) => matchingItemIds.has((j.itemId || "").toString().toLowerCase()));
950
1016
  }, [filteredJobs, statusFilter]);
951
1017
  if (isLoading && !jobs) {
952
- return (_jsxs("div", { className: "px-3 md:px-4 py-4 pl-8 text-[12px] text-gray-2", children: [_jsx(LoaderIcon, { className: "inline h-3.5 w-3.5 mr-1.5 animate-spin text-theme-secondary" }), "Loading items\u2026"] }));
1018
+ return (_jsxs("div", { className: "px-3 md:px-4 py-4 pl-8 text-[12px] text-neutral-grey-50", children: [_jsx(LoaderIcon, { className: "inline h-3.5 w-3.5 mr-1.5 animate-spin text-highlight-100" }), "Loading items\u2026"] }));
953
1019
  }
954
1020
  if (!statusFilteredJobs || statusFilteredJobs.length === 0) {
955
1021
  const hasItemFilter = itemFilter.trim() !== "";
956
1022
  const hasStatusFilter = statusFilter !== "all";
957
- return (_jsx("div", { className: "px-3 md:px-4 py-3 pl-8 text-[12px] text-gray-2", children: hasItemFilter || hasStatusFilter
1023
+ return (_jsx("div", { className: "px-3 md:px-4 py-3 pl-8 text-[12px] text-neutral-grey-50", children: hasItemFilter || hasStatusFilter
958
1024
  ? "No items match the current filter."
959
1025
  : "No items in this batch." }));
960
1026
  }
@@ -965,10 +1031,10 @@ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems,
965
1031
  byItem.set(key, []);
966
1032
  byItem.get(key).push(job);
967
1033
  }
968
- return (_jsx("div", { className: "bg-gray-5/60 divide-y divide-gray-3/50 border-t border-gray-3/50", children: Array.from(byItem.entries()).map(([itemId, itemJobs]) => {
1034
+ return (_jsx("div", { className: "bg-neutral-grey-5/60 divide-y divide-border-default/50 border-t border-border-default/50", children: Array.from(byItem.entries()).map(([itemId, itemJobs]) => {
969
1035
  const isItemExpanded = expandedItems.has(itemId);
970
- const itemAnyInProgress = itemJobs.some(j => j.status === "In Progress");
971
- const itemAnyPending = itemJobs.some(j => j.status === "Pending");
1036
+ const itemAnyInProgress = itemJobs.some((j) => j.status === "In Progress");
1037
+ const itemAnyPending = itemJobs.some((j) => j.status === "Pending");
972
1038
  const itemIsQueued = !itemAnyInProgress && itemAnyPending;
973
1039
  const status = itemStatusDot(itemJobs);
974
1040
  const firstJob = itemJobs[0];
@@ -976,28 +1042,28 @@ function BatchItemList({ batchId, batchProvider, jobs, isLoading, expandedItems,
976
1042
  const serverName = firstJob?.itemName ?? null;
977
1043
  const metadataName = parseItemName(firstJob?.metadata, "");
978
1044
  const displayName = metadataName || serverName || itemId;
979
- const langs = itemJobs.map(j => j.targetLanguage).filter(Boolean);
1045
+ const langs = itemJobs.map((j) => j.targetLanguage).filter(Boolean);
980
1046
  const uniqueLangs = Array.from(new Set(langs));
981
1047
  const titleAttr = `${itemPath ?? ""}\n${itemId}`.trim();
982
1048
  const itemErrorJobs = itemJobs.filter(isRetriableJob);
983
1049
  const itemRetryKey = `item:${batchId}:${itemId}`;
984
1050
  const isRetryingItem = retryingKeys.has(itemRetryKey);
985
- return (_jsxs("div", { children: [_jsx("div", { role: "button", tabIndex: 0, style: { paddingLeft: "2.5rem" }, className: "group w-full pr-3 md:pr-4 py-2 hover:bg-gray-5 transition-colors cursor-pointer text-left", onClick: () => onToggleItem(itemId), onKeyDown: (e) => {
1051
+ return (_jsxs("div", { children: [_jsx("div", { role: "button", tabIndex: 0, style: { paddingLeft: "2.5rem" }, className: "group w-full pr-3 md:pr-4 py-2 hover:bg-neutral-grey-5 transition-colors cursor-pointer text-left", onClick: () => onToggleItem(itemId), onKeyDown: (e) => {
986
1052
  if (e.key === "Enter" || e.key === " ") {
987
1053
  e.preventDefault();
988
1054
  onToggleItem(itemId);
989
1055
  }
990
- }, children: _jsxs("div", { className: "flex items-start gap-2.5 min-w-0", children: [_jsx("span", { className: "mt-1 shrink-0", children: itemAnyInProgress ? (_jsx(LoaderIcon, { className: "h-3 w-3 animate-spin text-theme-secondary" })) : itemIsQueued ? (_jsx(QueuedIcon, { className: "h-3 w-3 text-gray-2", strokeWidth: 1.75, "aria-label": "Queued" })) : (_jsx("span", { className: `block h-1.5 w-1.5 rounded-full ${status.cls}`, "aria-hidden": "true" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("span", { className: "text-[13px] text-(--color-dark-lighter) truncate", title: titleAttr, children: displayName }), _jsxs("span", { className: "text-[11px] text-gray-2 shrink-0", children: ["\u00B7 ", uniqueLangs.length, " language", uniqueLangs.length !== 1 ? 's' : ''] })] }), itemPath && (_jsx("div", { className: "text-[11px] text-gray-2 truncate font-mono", title: titleAttr, children: itemPath }))] }), itemErrorJobs.length > 0 && (_jsx(SimpleIconButton, { onClick: (event) => {
1056
+ }, children: _jsxs("div", { className: "flex items-start gap-2.5 min-w-0", children: [_jsx("span", { className: "mt-1 shrink-0", children: itemAnyInProgress ? (_jsx(LoaderIcon, { className: "h-3 w-3 animate-spin text-highlight-100" })) : itemIsQueued ? (_jsx(QueuedIcon, { className: "h-3 w-3 text-neutral-grey-50", strokeWidth: 1.75, "aria-label": "Queued" })) : (_jsx("span", { className: `block h-1.5 w-1.5 rounded-full ${status.cls}`, "aria-hidden": "true" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("span", { className: "text-[13px] text-neutral-grey-100 truncate", title: titleAttr, children: displayName }), _jsxs("span", { className: "text-[11px] text-neutral-grey-50 shrink-0", children: ["\u00B7 ", uniqueLangs.length, " language", uniqueLangs.length !== 1 ? "s" : ""] })] }), itemPath && (_jsx("div", { className: "text-[11px] text-neutral-grey-50 truncate font-mono", title: titleAttr, children: itemPath }))] }), itemErrorJobs.length > 0 && (_jsx(SimpleIconButton, { onClick: (event) => {
991
1057
  event.stopPropagation();
992
1058
  void onRetryJobs(batchId, batchProvider, itemErrorJobs, itemRetryKey);
993
- }, icon: _jsx(RetryIcon, { className: `h-3.5 w-3.5 ${isRetryingItem ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Retry failed translations for this item", disabled: isRetryingItem, className: "mt-0.5 p-0! text-red-600 hover:text-red-700" })), _jsx("span", { className: "mt-1 shrink-0 text-gray-3 group-hover:text-(--color-gray-2)", children: _jsx(ChevronDownIcon, { className: `h-3.5 w-3.5 transition-transform ${isItemExpanded ? 'rotate-180' : ''}` }) })] }) }), isItemExpanded && (_jsx("div", { style: { paddingLeft: "4rem" }, className: "pr-3 md:pr-4 pb-3 pt-1 flex flex-wrap gap-1.5", children: itemJobs.map((job, idx) => {
1059
+ }, icon: _jsx(RetryIcon, { className: `h-3.5 w-3.5 ${isRetryingItem ? "animate-spin" : ""}`, strokeWidth: 1.5 }), label: "Retry failed translations for this item", disabled: isRetryingItem, className: "mt-0.5 p-0! text-feedback-red hover:text-feedback-red" })), _jsx("span", { className: "mt-1 shrink-0 text-neutral-grey-15 group-hover:text-neutral-grey-50", children: _jsx(ChevronDownIcon, { className: `h-3.5 w-3.5 transition-transform ${isItemExpanded ? "rotate-180" : ""}` }) })] }) }), isItemExpanded && (_jsx("div", { style: { paddingLeft: "4rem" }, className: "pr-3 md:pr-4 pb-3 pt-1 flex flex-wrap gap-1.5", children: itemJobs.map((job, idx) => {
994
1060
  const lang = languageMap.get((job.targetLanguage || "").toLowerCase());
995
1061
  const retryKey = `job:${job.id ?? `${job.itemId}:${job.sourceLanguage}:${job.targetLanguage}`}`;
996
1062
  return (_jsx(LanguageJobChip, { job: job, language: lang, batchStartedAtUtc: batchStartedAtUtc, onOpen: () => onOpenItem(job.itemId, job.targetLanguage), isRetrying: retryingKeys.has(retryKey), onRetry: () => onRetryJobs(batchId, batchProvider, [job], retryKey) }, `${job.id ?? idx}-${job.targetLanguage}`));
997
1063
  }) }))] }, itemId));
998
1064
  }) }));
999
1065
  }
1000
- function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying = false, onRetry }) {
1066
+ function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying = false, onRetry, }) {
1001
1067
  const [open, setOpen] = React.useState(false);
1002
1068
  const jobStatus = job.status;
1003
1069
  const jobInProgress = jobStatus === "In Progress";
@@ -1006,28 +1072,30 @@ function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying
1006
1072
  const jobAborted = jobStatus === "Aborted";
1007
1073
  const langName = language?.name || job.targetLanguage;
1008
1074
  const chipCls = jobError
1009
- ? "border-red-200 bg-red-50 text-red-700 hover:bg-red-100"
1075
+ ? "border-feedback-red bg-feedback-red-light text-feedback-red hover:bg-feedback-red-light"
1010
1076
  : jobAborted
1011
- ? "border-amber-200 bg-amber-50 text-amber-700 hover:bg-amber-100"
1077
+ ? "border-feedback-orange bg-feedback-orange-light text-feedback-orange hover:bg-feedback-orange-light"
1012
1078
  : jobInProgress
1013
- ? "border-(--color-theme-secondary)/30 bg-theme-secondary-light text-theme-secondary hover:brightness-95"
1079
+ ? "border-highlight-100/30 bg-highlight-10 text-highlight-100 hover:brightness-95"
1014
1080
  : jobPending
1015
- ? "border-gray-3 bg-gray-5 text-gray-2 hover:bg-gray-4"
1016
- : "border-gray-3 bg-background text-(--color-gray-1) hover:bg-gray-5";
1081
+ ? "border-border-default bg-neutral-grey-5 text-neutral-grey-50 hover:bg-neutral-grey-5"
1082
+ : "border-border-default bg-background text-neutral-grey-100 hover:bg-neutral-grey-5";
1017
1083
  const statusBadgeCls = jobError
1018
- ? "bg-red-50 text-red-700 ring-1 ring-inset ring-red-200"
1084
+ ? "bg-feedback-red-light text-feedback-red ring-1 ring-inset ring-feedback-red"
1019
1085
  : jobAborted
1020
- ? "bg-amber-50 text-amber-700 ring-1 ring-inset ring-amber-200"
1086
+ ? "bg-feedback-orange-light text-feedback-orange ring-1 ring-inset ring-feedback-orange"
1021
1087
  : jobInProgress
1022
- ? "bg-theme-secondary-light text-theme-secondary ring-1 ring-inset ring-(--color-theme-secondary)/20"
1088
+ ? "bg-highlight-10 text-highlight-100 ring-1 ring-inset ring-highlight-100/20"
1023
1089
  : jobPending
1024
- ? "bg-gray-4 text-gray-2 ring-1 ring-inset ring-gray-3"
1025
- : "bg-emerald-50 text-emerald-700 ring-1 ring-inset ring-emerald-200";
1090
+ ? "bg-neutral-grey-5 text-neutral-grey-50 ring-1 ring-inset ring-border-default"
1091
+ : "bg-feedback-green-light text-feedback-green ring-1 ring-inset ring-feedback-green";
1026
1092
  const timestamp = job.timestamp ? new Date(job.timestamp) : null;
1027
1093
  const timestampValid = timestamp && !isNaN(timestamp.getTime());
1028
1094
  const claimedAt = job.claimedAtUtc ? new Date(job.claimedAtUtc) : null;
1029
1095
  const claimedAtValid = !!(claimedAt && !isNaN(claimedAt.getTime()));
1030
- const lastHeartbeat = job.lastHeartbeatUtc ? new Date(job.lastHeartbeatUtc) : null;
1096
+ const lastHeartbeat = job.lastHeartbeatUtc
1097
+ ? new Date(job.lastHeartbeatUtc)
1098
+ : null;
1031
1099
  const lastHeartbeatValid = !!(lastHeartbeat && !isNaN(lastHeartbeat.getTime()));
1032
1100
  // Fall back to the batch's start time when the job didn't record one of its
1033
1101
  // own — older jobs (and provider-less paths) skip ClaimedAtUtc.
@@ -1046,7 +1114,9 @@ function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying
1046
1114
  return null;
1047
1115
  const end = jobInProgress || jobPending
1048
1116
  ? Date.now()
1049
- : (timestampValid ? timestamp.getTime() : null);
1117
+ : timestampValid
1118
+ ? timestamp.getTime()
1119
+ : null;
1050
1120
  if (end == null)
1051
1121
  return null;
1052
1122
  const ms = end - startMs;
@@ -1076,7 +1146,9 @@ function LanguageJobChip({ job, language, batchStartedAtUtc, onOpen, isRetrying
1076
1146
  const emptyFieldText = statistics && statistics.emptyFieldCount > 0
1077
1147
  ? `${formatCount(statistics.emptyFieldCount)} empty`
1078
1148
  : null;
1079
- return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: `inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-[12px] transition-colors cursor-pointer ${chipCls}`, children: [language?.icon ? (_jsx("img", { src: language.icon, alt: langName, className: "h-3.5 shrink-0" })) : null, _jsx("span", { className: "font-mono tracking-tight", children: job.targetLanguage }), jobInProgress && (_jsx(LoaderIcon, { className: "h-3 w-3 shrink-0 animate-spin" })), jobPending && (_jsx(QueuedIcon, { className: "h-3 w-3 shrink-0", strokeWidth: 1.75, "aria-label": "Queued" })), jobError && (_jsx(AlertCircleIcon, { className: "h-3 w-3 shrink-0" })), !jobInProgress && !jobError && !jobPending && !jobAborted && (_jsx(CheckIcon, { className: "h-3 w-3 shrink-0 text-emerald-600" }))] }) }), _jsxs(PopoverContent, { align: "start", sideOffset: 6, className: "w-[22rem] max-w-[calc(100vw-2rem)] p-0", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-3 px-3 py-2", children: [language?.icon ? (_jsx("img", { src: language.icon, alt: langName, className: "h-4 shrink-0" })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-[13px] font-medium text-(--color-dark-lighter) truncate", children: langName }), _jsxs("div", { className: "text-[11px] text-gray-2 font-mono truncate", children: [job.targetLanguage, job.sourceLanguage ? ` ← ${job.sourceLanguage}` : ""] })] }), _jsxs("span", { className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${statusBadgeCls}`, children: [jobInProgress && _jsx(LoaderIcon, { className: "h-2.5 w-2.5 animate-spin" }), jobStatus || "Unknown"] })] }), _jsxs("dl", { className: "divide-y divide-gray-3/60", children: [claimedAtValid && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: "Claimed" }), _jsx("dd", { className: "text-[12px] text-(--color-gray-1)", children: claimedAt.toLocaleString() })] })), timestampValid && (jobInProgress || jobPending) && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: "Updated" }), _jsx("dd", { className: "text-[12px] text-(--color-gray-1)", children: timestamp.toLocaleString() })] })), durationMs != null && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: jobInProgress || jobPending ? "Running" : "Duration" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", title: durationApproximated ? "Approximate — measured from batch start" : undefined, children: [durationApproximated ? "~" : "", formatDuration(durationMs)] })] })), statistics && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: "Fields" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", children: [fieldText, emptyFieldText ? _jsxs("span", { className: "ml-1.5 text-gray-2", children: ["(", emptyFieldText, ")"] }) : null] })] })), statistics && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: "Text" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", children: [formatCount(statistics.sourceCharacterCount), " chars"] })] })), job.totalCost != null && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: "Cost" }), _jsx("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", children: formatUsdCost(job.totalCost) })] })), lastHeartbeatValid && (jobInProgress || jobPending) && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: "Heartbeat" }), _jsx("dd", { className: "text-[12px] text-(--color-gray-1)", children: lastHeartbeat.toLocaleString() })] })), attemptCount > 1 && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: "Attempts" }), _jsx("dd", { className: "text-[12px] tabular-nums text-(--color-gray-1)", children: attemptCount })] })), job.hash && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: "Hash" }), _jsx("dd", { className: "text-[11px] font-mono text-(--color-gray-1) break-all", children: job.hash })] })), job.message && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: jobError ? "Error" : jobAborted ? "Aborted" : "Message" }), _jsx("dd", { className: `text-[12px] break-words ${jobError ? "text-red-600" : jobAborted ? "text-amber-700" : "text-(--color-gray-1)"}`, children: job.message })] })), job.batchId && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] uppercase tracking-wide text-gray-2", children: "Batch" }), _jsx("dd", { className: "text-[11px] font-mono text-gray-2 break-all", children: job.batchId })] }))] }), _jsxs("div", { className: "flex justify-end gap-2 border-t border-gray-3 px-3 py-2", children: [jobError && onRetry && (_jsxs(Button, { size: "sm", variant: "outline", disabled: isRetrying, onClick: (e) => {
1149
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: `inline-flex items-center gap-1.5 rounded-md border badge-pad-sm text-[12px] transition-colors cursor-pointer ${chipCls}`, children: [language?.icon ? (_jsx("img", { src: language.icon, alt: langName, className: "h-3.5 shrink-0" })) : null, _jsx("span", { className: "font-mono tracking-tight", children: job.targetLanguage }), jobInProgress && (_jsx(LoaderIcon, { className: "h-3 w-3 shrink-0 animate-spin" })), jobPending && (_jsx(QueuedIcon, { className: "h-3 w-3 shrink-0", strokeWidth: 1.75, "aria-label": "Queued" })), jobError && _jsx(AlertCircleIcon, { className: "h-3 w-3 shrink-0" }), !jobInProgress && !jobError && !jobPending && !jobAborted && (_jsx(CheckIcon, { className: "h-3 w-3 shrink-0 text-feedback-green" }))] }) }), _jsxs(PopoverContent, { align: "start", sideOffset: 6, className: "w-[22rem] max-w-[calc(100vw-2rem)] p-0", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border-default px-3 py-2", children: [language?.icon ? (_jsx("img", { src: language.icon, alt: langName, className: "h-4 shrink-0" })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-[13px] font-medium text-neutral-grey-100 truncate", children: langName }), _jsxs("div", { className: "text-[11px] text-neutral-grey-50 font-mono truncate", children: [job.targetLanguage, job.sourceLanguage ? ` ← ${job.sourceLanguage}` : ""] })] }), _jsxs("span", { className: `inline-flex items-center gap-1 rounded-badge badge-pad-sm text-2xs font-semibold ${statusBadgeCls}`, children: [jobInProgress && (_jsx(LoaderIcon, { className: "h-2.5 w-2.5 animate-spin" })), jobStatus || "Unknown"] })] }), _jsxs("dl", { className: "divide-y divide-border-default/60", children: [claimedAtValid && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Claimed" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: claimedAt.toLocaleString() })] })), timestampValid && (jobInProgress || jobPending) && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Updated" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: timestamp.toLocaleString() })] })), durationMs != null && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: jobInProgress || jobPending ? "Running" : "Duration" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", title: durationApproximated
1150
+ ? "Approximate — measured from batch start"
1151
+ : undefined, children: [durationApproximated ? "~" : "", formatDuration(durationMs)] })] })), statistics && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Fields" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: [fieldText, emptyFieldText ? (_jsxs("span", { className: "ml-1.5 text-neutral-grey-50", children: ["(", emptyFieldText, ")"] })) : null] })] })), statistics && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Text" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: [formatCount(statistics.sourceCharacterCount), " chars"] })] })), job.totalCost != null && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Cost" }), _jsx("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: formatUsdCost(job.totalCost) })] })), lastHeartbeatValid && (jobInProgress || jobPending) && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Heartbeat" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: lastHeartbeat.toLocaleString() })] })), attemptCount > 1 && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Attempts" }), _jsx("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: attemptCount })] })), job.hash && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Hash" }), _jsx("dd", { className: "text-[11px] font-mono text-neutral-grey-100 break-all", children: job.hash })] })), job.message && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: jobError ? "Error" : jobAborted ? "Aborted" : "Message" }), _jsx("dd", { className: `text-[12px] break-words ${jobError ? "text-feedback-red" : jobAborted ? "text-feedback-orange" : "text-neutral-grey-100"}`, children: job.message })] })), job.batchId && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Batch" }), _jsx("dd", { className: "text-[11px] font-mono text-neutral-grey-50 break-all", children: job.batchId })] }))] }), _jsxs("div", { className: "flex justify-end gap-2 border-t border-border-default px-3 py-2", children: [jobError && onRetry && (_jsxs(Button, { size: "sm", variant: "outline", disabled: isRetrying, onClick: (e) => {
1080
1152
  e.stopPropagation();
1081
1153
  void onRetry();
1082
1154
  setOpen(false);