@cat-factory/app 0.54.3 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/app/app.vue +14 -0
  2. package/app/components/auth/UserMenu.vue +1 -1
  3. package/app/components/board/AddTaskModal.vue +5 -4
  4. package/app/components/board/nodes/AgentChip.vue +2 -2
  5. package/app/components/board/nodes/BlockNode.vue +12 -8
  6. package/app/components/board/nodes/DraggableTask.vue +1 -1
  7. package/app/components/board/nodes/EpicNode.vue +1 -1
  8. package/app/components/board/nodes/ModuleFrame.vue +7 -4
  9. package/app/components/board/nodes/TaskCard.vue +2 -2
  10. package/app/components/board/nodes/TaskPipelineMini.vue +3 -3
  11. package/app/components/brainstorm/BrainstormWindow.vue +8 -7
  12. package/app/components/clarity/ClarityReviewWindow.vue +8 -7
  13. package/app/components/consensus/ConsensusSessionWindow.vue +2 -2
  14. package/app/components/documents/ContextDocumentPicker.vue +4 -4
  15. package/app/components/documents/SpawnPreviewModal.vue +4 -4
  16. package/app/components/focus/BlockFocusView.vue +11 -4
  17. package/app/components/fragments/FragmentLibraryManager.vue +5 -5
  18. package/app/components/gates/GateResultView.vue +3 -3
  19. package/app/components/github/GitHubPanel.vue +5 -2
  20. package/app/components/humanTest/HumanTestWindow.vue +1 -1
  21. package/app/components/kaizen/KaizenPanel.vue +5 -5
  22. package/app/components/kaizen/KaizenStepStatus.vue +2 -2
  23. package/app/components/layout/BoardSwitcher.vue +4 -4
  24. package/app/components/layout/CommandBar.vue +1 -1
  25. package/app/components/layout/IntegrationBackTitle.vue +2 -1
  26. package/app/components/layout/IntegrationsHub.vue +10 -4
  27. package/app/components/layout/LanguageSwitcher.vue +1 -1
  28. package/app/components/layout/NotificationsInbox.vue +1 -1
  29. package/app/components/layout/PersonalSetupModal.vue +5 -2
  30. package/app/components/layout/SideBar.vue +5 -3
  31. package/app/components/media/ArtifactLightbox.vue +5 -5
  32. package/app/components/media/ImageCompare.vue +6 -1
  33. package/app/components/observability/StepMetricsBar.vue +6 -2
  34. package/app/components/palettes/AgentPalette.vue +4 -4
  35. package/app/components/panels/AgentStepDetail.vue +12 -12
  36. package/app/components/panels/GenericStructuredResultView.vue +1 -1
  37. package/app/components/panels/InspectorPanel.vue +2 -2
  38. package/app/components/panels/ObservabilityPanel.vue +6 -6
  39. package/app/components/panels/StepMetadataCard.vue +1 -1
  40. package/app/components/panels/inspector/ContainerSummary.vue +2 -2
  41. package/app/components/panels/inspector/EpicChildren.vue +3 -3
  42. package/app/components/panels/inspector/RecurringScheduleSettings.vue +1 -1
  43. package/app/components/panels/inspector/ServiceFragments.vue +1 -1
  44. package/app/components/panels/inspector/ServiceTestConfig.vue +1 -1
  45. package/app/components/panels/inspector/TaskDependencies.vue +1 -1
  46. package/app/components/panels/inspector/TaskEstimateBadge.vue +1 -1
  47. package/app/components/panels/inspector/TaskExecution.vue +9 -9
  48. package/app/components/panels/inspector/TaskRunSettings.vue +2 -2
  49. package/app/components/panels/inspector/TaskStructure.vue +1 -1
  50. package/app/components/pipeline/IterationCapPrompt.vue +1 -0
  51. package/app/components/pipeline/PipelineBuilder.vue +8 -8
  52. package/app/components/pipeline/PipelineProgress.vue +4 -4
  53. package/app/components/providers/ApiKeysSection.vue +2 -2
  54. package/app/components/providers/PersonalSubscriptionSection.vue +2 -2
  55. package/app/components/providers/VendorCredentialsModal.vue +2 -2
  56. package/app/components/provisioning/ProvisioningLogsDrawer.vue +1 -1
  57. package/app/components/requirements/RequirementsReviewWindow.vue +9 -8
  58. package/app/components/sandbox/SandboxPanel.vue +17 -17
  59. package/app/components/settings/LocalModelEndpointsPanel.vue +1 -1
  60. package/app/components/settings/ModelConfigurationPanel.vue +4 -3
  61. package/app/components/settings/OpenRouterCatalogPanel.vue +3 -3
  62. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +1 -1
  63. package/app/components/spec/ServiceSpecWindow.vue +3 -3
  64. package/app/components/tasks/ContextIssuePicker.vue +5 -5
  65. package/app/components/tasks/TaskContextIssues.vue +1 -1
  66. package/app/components/tasks/TaskImportModal.vue +1 -1
  67. package/app/components/testing/TestReportWindow.vue +5 -5
  68. package/app/components/visualConfirm/VisualConfirmationWindow.vue +2 -2
  69. package/app/pages/index.vue +1 -1
  70. package/i18n/i18n.config.ts +9 -0
  71. package/i18n/locales/he.json +3385 -0
  72. package/nuxt.config.ts +1 -0
  73. package/package.json +2 -2
@@ -69,7 +69,7 @@ function statusLabel(g: KaizenGrading): string {
69
69
  {{ t('kaizen.subtitle') }}
70
70
  </p>
71
71
  </div>
72
- <div class="ml-auto flex items-center gap-2">
72
+ <div class="ms-auto flex items-center gap-2">
73
73
  <UButton
74
74
  icon="i-lucide-refresh-cw"
75
75
  size="xs"
@@ -124,9 +124,9 @@ function statusLabel(g: KaizenGrading): string {
124
124
  <UIcon
125
125
  v-if="c.verified"
126
126
  name="i-lucide-badge-check"
127
- class="ml-auto h-3.5 w-3.5 text-emerald-400"
127
+ class="ms-auto h-3.5 w-3.5 text-emerald-400"
128
128
  />
129
- <span v-else class="ml-auto text-[11px] text-slate-500">
129
+ <span v-else class="ms-auto text-[11px] text-slate-500">
130
130
  {{ t('kaizen.verifiedCombos.progress', { count: c.consecutiveHighGrades }) }}
131
131
  </span>
132
132
  </div>
@@ -152,7 +152,7 @@ function statusLabel(g: KaizenGrading): string {
152
152
  {{ t('kaizen.history.title') }}
153
153
  </h2>
154
154
  <div class="overflow-hidden rounded-lg border border-slate-800">
155
- <table class="w-full text-left text-xs">
155
+ <table class="w-full text-start text-xs">
156
156
  <thead class="bg-slate-900/60 text-[11px] uppercase tracking-wide text-slate-500">
157
157
  <tr>
158
158
  <th class="px-3 py-2 font-medium">{{ t('kaizen.history.col.when') }}</th>
@@ -189,7 +189,7 @@ function statusLabel(g: KaizenGrading): string {
189
189
  {{ statusLabel(g) }}
190
190
  </td>
191
191
  <td class="px-3 py-2 text-slate-400">
192
- <ul v-if="g.recommendations.length" class="list-disc space-y-0.5 pl-4">
192
+ <ul v-if="g.recommendations.length" class="list-disc space-y-0.5 ps-4">
193
193
  <li v-for="(r, i) in g.recommendations" :key="i">{{ r }}</li>
194
194
  </ul>
195
195
  <span v-else-if="g.status === 'complete'" class="text-slate-600">—</span>
@@ -47,7 +47,7 @@ const tone = computed(() => {
47
47
  <div class="flex items-center gap-2">
48
48
  <UIcon name="i-lucide-sparkles" class="h-4 w-4 text-teal-400" />
49
49
  <h3 class="text-sm font-semibold text-slate-200">{{ t('kaizen.grading.title') }}</h3>
50
- <span class="ml-auto flex items-center gap-1.5 text-xs">
50
+ <span class="ms-auto flex items-center gap-1.5 text-xs">
51
51
  <template v-if="grading.status === 'scheduled'">
52
52
  <UIcon name="i-lucide-clock" class="h-3.5 w-3.5 text-slate-500" />
53
53
  <span class="text-slate-400">{{ t('kaizen.status.scheduled') }}</span>
@@ -78,7 +78,7 @@ const tone = computed(() => {
78
78
  <p class="text-[11px] font-medium uppercase tracking-wide text-slate-500">
79
79
  {{ t('kaizen.recommendations') }}
80
80
  </p>
81
- <ul class="mt-1 list-disc space-y-0.5 pl-4 text-xs text-slate-300">
81
+ <ul class="mt-1 list-disc space-y-0.5 ps-4 text-xs text-slate-300">
82
82
  <li v-for="(r, i) in grading.recommendations" :key="i">{{ r }}</li>
83
83
  </ul>
84
84
  </div>
@@ -232,7 +232,7 @@ async function submitPrompt() {
232
232
  >
233
233
  <button
234
234
  type="button"
235
- class="flex w-full items-center gap-2 rounded-md px-1.5 py-1 text-left transition hover:bg-slate-800/60"
235
+ class="flex w-full items-center gap-2 rounded-md px-1.5 py-1 text-start transition hover:bg-slate-800/60"
236
236
  :disabled="busy"
237
237
  >
238
238
  <UIcon
@@ -244,7 +244,7 @@ async function submitPrompt() {
244
244
  </span>
245
245
  <UIcon
246
246
  name="i-lucide-chevrons-up-down"
247
- class="ml-auto h-3.5 w-3.5 shrink-0 text-slate-600"
247
+ class="ms-auto h-3.5 w-3.5 shrink-0 text-slate-600"
248
248
  />
249
249
  </button>
250
250
  </UDropdownMenu>
@@ -253,14 +253,14 @@ async function submitPrompt() {
253
253
  <UDropdownMenu :items="boardItems" :content="{ align: 'start' }" class="w-full">
254
254
  <button
255
255
  type="button"
256
- class="flex w-full items-center gap-2 rounded-lg border border-slate-800 bg-slate-900/60 px-2.5 py-1.5 text-left transition hover:bg-slate-800/60"
256
+ class="flex w-full items-center gap-2 rounded-lg border border-slate-800 bg-slate-900/60 px-2.5 py-1.5 text-start transition hover:bg-slate-800/60"
257
257
  :disabled="busy"
258
258
  >
259
259
  <UIcon name="i-lucide-layout-dashboard" class="h-4 w-4 shrink-0 text-indigo-400" />
260
260
  <span class="truncate text-sm font-medium text-white">
261
261
  {{ workspace.activeWorkspace?.name ?? t('layout.boardSwitcher.boardFallback') }}
262
262
  </span>
263
- <UIcon name="i-lucide-chevron-down" class="ml-auto h-4 w-4 shrink-0 text-slate-500" />
263
+ <UIcon name="i-lucide-chevron-down" class="ms-auto h-4 w-4 shrink-0 text-slate-500" />
264
264
  </button>
265
265
  </UDropdownMenu>
266
266
 
@@ -321,7 +321,7 @@ function indexOf(cmd: Command) {
321
321
  v-for="cmd in group.items"
322
322
  :key="cmd.id"
323
323
  type="button"
324
- class="flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left text-sm transition"
324
+ class="flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-start text-sm transition"
325
325
  :class="
326
326
  indexOf(cmd) === activeIndex
327
327
  ? 'bg-slate-800 text-slate-100'
@@ -23,10 +23,11 @@ const backLabel = computed(() =>
23
23
  <UButton
24
24
  v-if="cameFromHub"
25
25
  icon="i-lucide-arrow-left"
26
+ :ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
26
27
  color="neutral"
27
28
  variant="ghost"
28
29
  size="xs"
29
- class="-ml-1.5 shrink-0"
30
+ class="-ms-1.5 shrink-0"
30
31
  :aria-label="backLabel"
31
32
  @click.stop="emit('back')"
32
33
  />
@@ -390,7 +390,7 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
390
390
  v-for="item in group.items"
391
391
  :key="item.key"
392
392
  type="button"
393
- class="flex w-full items-center gap-3 rounded-lg border border-slate-800 bg-slate-900/50 px-3 py-2.5 text-left transition hover:border-slate-700 hover:bg-slate-900"
393
+ class="flex w-full items-center gap-3 rounded-lg border border-slate-800 bg-slate-900/50 px-3 py-2.5 text-start transition hover:border-slate-700 hover:bg-slate-900"
394
394
  @click="item.onClick()"
395
395
  >
396
396
  <UIcon :name="item.icon" class="h-5 w-5 shrink-0 text-slate-300" />
@@ -417,7 +417,10 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
417
417
  </div>
418
418
  <p class="truncate text-xs text-slate-400">{{ item.description }}</p>
419
419
  </div>
420
- <UIcon name="i-lucide-chevron-right" class="h-4 w-4 shrink-0 text-slate-500" />
420
+ <UIcon
421
+ name="i-lucide-chevron-right"
422
+ class="h-4 w-4 shrink-0 text-slate-500 rtl:-scale-x-100"
423
+ />
421
424
  </button>
422
425
  </div>
423
426
 
@@ -425,7 +428,7 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
425
428
  <button
426
429
  v-if="group.footerLink"
427
430
  type="button"
428
- class="mt-1.5 flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-left text-xs text-slate-400 transition hover:bg-slate-900/60 hover:text-slate-200"
431
+ class="mt-1.5 flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-start text-xs text-slate-400 transition hover:bg-slate-900/60 hover:text-slate-200"
429
432
  @click="group.footerLink.onClick()"
430
433
  >
431
434
  <UIcon :name="group.footerLink.icon" class="h-3.5 w-3.5 shrink-0" />
@@ -433,7 +436,10 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
433
436
  <span v-if="group.footerLink.status" class="shrink-0 text-slate-500">{{
434
437
  group.footerLink.status
435
438
  }}</span>
436
- <UIcon name="i-lucide-chevron-right" class="h-3.5 w-3.5 shrink-0 text-slate-600" />
439
+ <UIcon
440
+ name="i-lucide-chevron-right"
441
+ class="h-3.5 w-3.5 shrink-0 text-slate-600 rtl:-scale-x-100"
442
+ />
437
443
  </button>
438
444
  </section>
439
445
  </div>
@@ -39,7 +39,7 @@ const items = computed<DropdownMenuItem[][]>(() => [
39
39
  type="button"
40
40
  data-testid="language-switcher"
41
41
  :aria-label="t('language.switcher')"
42
- class="flex w-full items-center gap-2 rounded-lg border border-slate-800 bg-slate-900/60 p-2 text-left transition hover:bg-slate-800/60"
42
+ class="flex w-full items-center gap-2 rounded-lg border border-slate-800 bg-slate-900/60 p-2 text-start transition hover:bg-slate-800/60"
43
43
  >
44
44
  <UIcon name="i-lucide-languages" class="h-4 w-4 shrink-0 text-slate-400" />
45
45
  <div class="min-w-0 flex-1">
@@ -226,7 +226,7 @@ function revealDecision(n: Notification) {
226
226
  <div class="min-w-0 flex-1">
227
227
  <div class="flex items-center gap-1.5">
228
228
  <button
229
- class="block min-w-0 flex-1 truncate text-left text-sm font-medium text-slate-200 hover:underline"
229
+ class="block min-w-0 flex-1 truncate text-start text-sm font-medium text-slate-200 hover:underline"
230
230
  :title="n.title"
231
231
  @click="reveal(n)"
232
232
  >
@@ -125,7 +125,7 @@ const groups = computed<PersonalGroup[]>(() => {
125
125
  v-for="item in group.items"
126
126
  :key="item.key"
127
127
  type="button"
128
- class="flex w-full items-center gap-3 rounded-lg border border-slate-800 bg-slate-900/50 px-3 py-2.5 text-left transition hover:border-slate-700 hover:bg-slate-900"
128
+ class="flex w-full items-center gap-3 rounded-lg border border-slate-800 bg-slate-900/50 px-3 py-2.5 text-start transition hover:border-slate-700 hover:bg-slate-900"
129
129
  @click="item.onClick()"
130
130
  >
131
131
  <UIcon :name="item.icon" class="h-5 w-5 shrink-0 text-slate-300" />
@@ -141,7 +141,10 @@ const groups = computed<PersonalGroup[]>(() => {
141
141
  </div>
142
142
  <p class="truncate text-xs text-slate-400">{{ item.description }}</p>
143
143
  </div>
144
- <UIcon name="i-lucide-chevron-right" class="h-4 w-4 shrink-0 text-slate-500" />
144
+ <UIcon
145
+ name="i-lucide-chevron-right"
146
+ class="h-4 w-4 shrink-0 text-slate-500 rtl:-scale-x-100"
147
+ />
145
148
  </button>
146
149
  </div>
147
150
  </section>
@@ -139,8 +139,10 @@ watch(
139
139
  :aria-modal="drawerOpen ? 'true' : undefined"
140
140
  :aria-label="isCompact ? t('nav.menu') : undefined"
141
141
  :inert="isCompact && !ui.mobileNavOpen"
142
- class="fixed inset-y-0 left-0 z-40 flex h-full w-64 shrink-0 flex-col gap-4 overflow-y-auto border-r border-slate-800 bg-slate-900/95 p-3 backdrop-blur transition-transform duration-200 focus:outline-none lg:static lg:z-auto lg:translate-x-0 lg:bg-slate-900/80"
143
- :class="ui.mobileNavOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'"
142
+ class="fixed inset-y-0 start-0 z-40 flex h-full w-64 shrink-0 flex-col gap-4 overflow-y-auto border-e border-slate-800 bg-slate-900/95 p-3 backdrop-blur transition-transform duration-200 focus:outline-none lg:static lg:z-auto lg:translate-x-0 lg:bg-slate-900/80"
143
+ :class="
144
+ ui.mobileNavOpen ? 'translate-x-0' : '-translate-x-full rtl:translate-x-full lg:translate-x-0'
145
+ "
144
146
  >
145
147
  <BoardSwitcher />
146
148
 
@@ -149,7 +151,7 @@ watch(
149
151
  and reach every action below. -->
150
152
  <button
151
153
  type="button"
152
- class="flex items-center gap-2 rounded-lg border border-slate-700 bg-slate-800/60 px-2.5 py-2 text-left text-sm text-slate-400 transition hover:border-slate-500 hover:bg-slate-800"
154
+ class="flex items-center gap-2 rounded-lg border border-slate-700 bg-slate-800/60 px-2.5 py-2 text-start text-sm text-slate-400 transition hover:border-slate-500 hover:bg-slate-800"
153
155
  @click="ui.openCommandBar()"
154
156
  >
155
157
  <UIcon name="i-lucide-search" class="h-4 w-4 shrink-0" />
@@ -206,7 +206,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
206
206
  <UIcon name="i-lucide-maximize" class="h-4 w-4" />
207
207
  </button>
208
208
  <button
209
- class="ml-1 rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
209
+ class="ms-1 rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
210
210
  :title="t('media.lightbox.close')"
211
211
  @click="close"
212
212
  >
@@ -223,11 +223,11 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
223
223
  >
224
224
  <button
225
225
  v-if="total > 1"
226
- class="absolute left-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-slate-900/80 p-2 text-slate-300 hover:bg-slate-800 hover:text-white"
226
+ class="absolute start-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-slate-900/80 p-2 text-slate-300 hover:bg-slate-800 hover:text-white"
227
227
  :title="t('media.lightbox.prev')"
228
228
  @click="go(-1)"
229
229
  >
230
- <UIcon name="i-lucide-chevron-left" class="h-5 w-5" />
230
+ <UIcon name="i-lucide-chevron-left" class="h-5 w-5 rtl:-scale-x-100" />
231
231
  </button>
232
232
 
233
233
  <img
@@ -267,11 +267,11 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
267
267
 
268
268
  <button
269
269
  v-if="total > 1"
270
- class="absolute right-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-slate-900/80 p-2 text-slate-300 hover:bg-slate-800 hover:text-white"
270
+ class="absolute end-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-slate-900/80 p-2 text-slate-300 hover:bg-slate-800 hover:text-white"
271
271
  :title="t('media.lightbox.next')"
272
272
  @click="go(1)"
273
273
  >
274
- <UIcon name="i-lucide-chevron-right" class="h-5 w-5" />
274
+ <UIcon name="i-lucide-chevron-right" class="h-5 w-5 rtl:-scale-x-100" />
275
275
  </button>
276
276
  </div>
277
277
  </div>
@@ -208,7 +208,7 @@ function onRefInput(e: Event) {
208
208
  class="w-full cursor-zoom-in"
209
209
  />
210
210
  <span
211
- class="absolute bottom-1 right-1 rounded bg-slate-950/80 px-1.5 py-0.5 text-[10px] text-slate-300 opacity-0 group-hover:opacity-100"
211
+ class="absolute bottom-1 end-1 rounded bg-slate-950/80 px-1.5 py-0.5 text-[10px] text-slate-300 opacity-0 group-hover:opacity-100"
212
212
  @click.stop="refInput?.click()"
213
213
  >
214
214
  {{ t('media.compare.replace') }}
@@ -282,6 +282,11 @@ function onRefInput(e: Event) {
282
282
  class="absolute inset-0 block h-full w-full object-contain"
283
283
  />
284
284
  </div>
285
+ <!-- The split geometry is physical and unmirrored (the divider is positioned by a
286
+ `left` percent, the clip-path insets from the right, and the pointer math reads
287
+ `clientX - rect.left`), so the handle and the corner labels stay PHYSICAL too.
288
+ Logical (`start-`/`end-`) here would flip the labels under RTL while the image
289
+ regions stayed put, mislabeling actual vs reference. -->
285
290
  <div class="absolute inset-y-0 w-0.5 bg-amber-400" :style="{ left: `${splitPct}%` }">
286
291
  <span
287
292
  class="absolute top-1/2 left-1/2 flex h-6 w-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-amber-400 text-slate-950 shadow"
@@ -61,14 +61,18 @@ const headroomTone = computed(() => headroomColor(headroom.value, m.value.trunca
61
61
  })
62
62
  }}
63
63
  </span>
64
- <div class="ml-auto flex items-center gap-1">
64
+ <div class="ms-auto flex items-center gap-1">
65
65
  <UBadge v-if="m.errors > 0" color="error" variant="subtle" size="sm">
66
66
  {{ t('observability.metricsBar.errors', { count: m.errors }, m.errors) }}
67
67
  </UBadge>
68
68
  <UBadge v-if="m.warnings > 0" color="warning" variant="subtle" size="sm">
69
69
  {{ t('observability.metricsBar.warnings', { count: m.warnings }, m.warnings) }}
70
70
  </UBadge>
71
- <UIcon v-if="clickable" name="i-lucide-chevron-right" class="h-3.5 w-3.5 text-slate-600" />
71
+ <UIcon
72
+ v-if="clickable"
73
+ name="i-lucide-chevron-right"
74
+ class="h-3.5 w-3.5 text-slate-600 rtl:-scale-x-100"
75
+ />
72
76
  </div>
73
77
  </div>
74
78
 
@@ -50,7 +50,7 @@ function toggle(id: string) {
50
50
  <section v-for="g in groups" :key="g.id">
51
51
  <button
52
52
  type="button"
53
- class="flex w-full items-center gap-1.5 rounded px-1 py-1 text-left text-[11px] font-semibold uppercase tracking-wide text-slate-400 transition hover:text-slate-200"
53
+ class="flex w-full items-center gap-1.5 rounded px-1 py-1 text-start text-[11px] font-semibold uppercase tracking-wide text-slate-400 transition hover:text-slate-200"
54
54
  @click="toggle(g.id)"
55
55
  >
56
56
  <UIcon
@@ -58,14 +58,14 @@ function toggle(id: string) {
58
58
  class="h-3.5 w-3.5 shrink-0"
59
59
  />
60
60
  <span>{{ g.label }}</span>
61
- <span class="ml-auto text-slate-600">{{ g.agents.length }}</span>
61
+ <span class="ms-auto text-slate-600">{{ g.agents.length }}</span>
62
62
  </button>
63
63
  <div v-if="!isCollapsed(g.id)" class="mt-1 space-y-1.5">
64
64
  <button
65
65
  v-for="a in g.agents"
66
66
  :key="a.kind"
67
67
  type="button"
68
- class="flex w-full items-center gap-2.5 rounded-lg border border-slate-700 bg-slate-800/60 p-2 text-left transition hover:border-slate-500 hover:bg-slate-800"
68
+ class="flex w-full items-center gap-2.5 rounded-lg border border-slate-700 bg-slate-800/60 p-2 text-start transition hover:border-slate-500 hover:bg-slate-800"
69
69
  :title="a.description"
70
70
  @click="$emit('add', a.kind)"
71
71
  >
@@ -79,7 +79,7 @@ function toggle(id: string) {
79
79
  <div class="text-xs font-semibold text-slate-100">{{ a.label }}</div>
80
80
  <div class="truncate text-[10px] text-slate-400">{{ a.description }}</div>
81
81
  </div>
82
- <UIcon name="i-lucide-plus" class="ml-auto h-4 w-4 shrink-0 text-slate-500" />
82
+ <UIcon name="i-lucide-plus" class="ms-auto h-4 w-4 shrink-0 text-slate-500" />
83
83
  </button>
84
84
  </div>
85
85
  </section>
@@ -185,7 +185,7 @@ async function copyOutput() {
185
185
  <!-- ToC sidebar (only meaningful when there are prose headings) -->
186
186
  <aside
187
187
  v-if="outline.hasToc"
188
- class="hidden w-72 shrink-0 flex-col border-r border-slate-800 bg-slate-900/60 md:flex"
188
+ class="hidden w-72 shrink-0 flex-col border-e border-slate-800 bg-slate-900/60 md:flex"
189
189
  >
190
190
  <div class="border-b border-slate-800 px-4 py-3">
191
191
  <div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
@@ -194,7 +194,7 @@ async function copyOutput() {
194
194
  </div>
195
195
  <nav class="flex-1 space-y-0.5 overflow-auto px-2 py-3">
196
196
  <button
197
- class="block w-full truncate rounded-md px-2 py-1 text-left text-[13px] transition"
197
+ class="block w-full truncate rounded-md px-2 py-1 text-start text-[13px] transition"
198
198
  :class="
199
199
  activeId === 'step-details'
200
200
  ? 'bg-indigo-500/15 font-medium text-indigo-200'
@@ -207,7 +207,7 @@ async function copyOutput() {
207
207
  <button
208
208
  v-for="s in tocSections"
209
209
  :key="s.id"
210
- class="block w-full truncate rounded-md px-2 py-1 text-left text-[13px] transition"
210
+ class="block w-full truncate rounded-md px-2 py-1 text-start text-[13px] transition"
211
211
  :class="
212
212
  activeId === s.id
213
213
  ? 'bg-indigo-500/15 font-medium text-indigo-200'
@@ -235,15 +235,15 @@ async function copyOutput() {
235
235
  <h1 class="truncate text-base font-semibold text-white">{{ agent.label }}</h1>
236
236
  <p v-if="block" class="truncate text-xs text-slate-500">{{ block.title }}</p>
237
237
  </div>
238
- <div class="ml-auto flex items-center gap-1.5">
238
+ <div class="ms-auto flex items-center gap-1.5">
239
239
  <UBadge
240
240
  v-if="approvalPending && !companionExceeded"
241
241
  color="warning"
242
242
  variant="subtle"
243
243
  size="sm"
244
- class="mr-1"
244
+ class="me-1"
245
245
  >
246
- <UIcon name="i-lucide-shield-check" class="mr-1 h-3 w-3" />
246
+ <UIcon name="i-lucide-shield-check" class="me-1 h-3 w-3" />
247
247
  {{ t('panels.stepDetail.approvalRequired') }}
248
248
  </UBadge>
249
249
  <UBadge
@@ -251,9 +251,9 @@ async function copyOutput() {
251
251
  color="warning"
252
252
  variant="subtle"
253
253
  size="sm"
254
- class="mr-1"
254
+ class="me-1"
255
255
  >
256
- <UIcon name="i-lucide-alert-triangle" class="mr-1 h-3 w-3" />
256
+ <UIcon name="i-lucide-alert-triangle" class="me-1 h-3 w-3" />
257
257
  {{ t('panels.stepDetail.decisionRequired') }}
258
258
  </UBadge>
259
259
  <UButton
@@ -403,7 +403,7 @@ async function copyOutput() {
403
403
  >
404
404
  <button
405
405
  v-if="s.depth > 0"
406
- class="group flex w-full items-center gap-2 rounded-md py-1 text-left transition hover:text-white"
406
+ class="group flex w-full items-center gap-2 rounded-md py-1 text-start transition hover:text-white"
407
407
  @click="toggle(s.id)"
408
408
  >
409
409
  <UIcon
@@ -422,7 +422,7 @@ async function copyOutput() {
422
422
  v-show="!collapsed[s.id]"
423
423
  class="reader-prose mt-1 text-[13px] leading-relaxed text-slate-300"
424
424
  :class="[
425
- s.depth > 0 ? 'pl-6' : '',
425
+ s.depth > 0 ? 'ps-6' : '',
426
426
  approvalPending && !editing && !companionExceeded ? 'review-mode' : '',
427
427
  ]"
428
428
  @click="onProseClick"
@@ -442,11 +442,11 @@ async function copyOutput() {
442
442
  </div>
443
443
 
444
444
  <!-- review rail (approval mode): per-block comments + overall feedback +
445
- Approve / Request changes / Reject. A right-side rail on wide screens; a
445
+ Approve / Request changes / Reject. A end-side rail on wide screens; a
446
446
  bottom sheet (still reachable) below lg, so the gate is always actionable. -->
447
447
  <aside
448
448
  v-if="approvalPending && !companionExceeded"
449
- class="absolute inset-x-0 bottom-0 z-10 flex max-h-[70dvh] flex-col rounded-t-2xl border-t border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur lg:static lg:inset-auto lg:z-auto lg:max-h-none lg:w-96 lg:shrink-0 lg:rounded-none lg:border-l lg:border-t-0 lg:border-slate-800 lg:bg-slate-900/60 lg:shadow-none lg:backdrop-blur-none"
449
+ class="absolute inset-x-0 bottom-0 z-10 flex max-h-[70dvh] flex-col rounded-t-2xl border-t border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur lg:static lg:inset-auto lg:z-auto lg:max-h-none lg:w-96 lg:shrink-0 lg:rounded-none lg:border-s lg:border-t-0 lg:border-slate-800 lg:bg-slate-900/60 lg:shadow-none lg:backdrop-blur-none"
450
450
  >
451
451
  <div class="border-b border-slate-800 px-4 py-3">
452
452
  <div class="text-[11px] font-semibold uppercase tracking-wide text-amber-400">
@@ -122,7 +122,7 @@ const customJson = computed<string | null>(() => {
122
122
 
123
123
  <!-- Sidebar: shared run metadata + observability rollup -->
124
124
  <aside
125
- class="hidden w-60 shrink-0 flex-col gap-4 border-l border-slate-800 bg-slate-900/50 px-4 py-4 lg:flex"
125
+ class="hidden w-60 shrink-0 flex-col gap-4 border-s border-slate-800 bg-slate-900/50 px-4 py-4 lg:flex"
126
126
  >
127
127
  <StepRunMeta
128
128
  v-if="step"
@@ -207,7 +207,7 @@ const showOriginalDescription = ref(false)
207
207
  <div
208
208
  v-if="block && statusMeta && typeMeta"
209
209
  data-testid="inspector-panel"
210
- class="fixed inset-x-0 bottom-0 z-20 overflow-hidden rounded-t-2xl border border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur lg:absolute lg:inset-x-auto lg:bottom-auto lg:right-4 lg:top-4 lg:w-80 lg:rounded-2xl"
210
+ class="fixed inset-x-0 bottom-0 z-20 overflow-hidden rounded-t-2xl border border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur lg:absolute lg:inset-x-auto lg:bottom-auto lg:end-4 lg:top-4 lg:w-80 lg:rounded-2xl"
211
211
  >
212
212
  <div class="h-1.5 w-full" :style="{ backgroundColor: statusMeta.color }" />
213
213
  <!-- A tall task (execution steps + scenarios + docs) can overflow the
@@ -487,7 +487,7 @@ const showOriginalDescription = ref(false)
487
487
  variant="ghost"
488
488
  size="sm"
489
489
  icon="i-lucide-trash-2"
490
- class="ml-auto"
490
+ class="ms-auto"
491
491
  :title="deleteLabel"
492
492
  @click="remove"
493
493
  >
@@ -158,8 +158,8 @@ function exportJson() {
158
158
  {{ block.title }} · {{ instance?.pipelineName }}
159
159
  </p>
160
160
  </div>
161
- <div class="ml-auto flex items-center gap-1.5">
162
- <div class="mr-1 flex rounded-lg border border-slate-800 p-0.5 text-[12px]">
161
+ <div class="ms-auto flex items-center gap-1.5">
162
+ <div class="me-1 flex rounded-lg border border-slate-800 p-0.5 text-[12px]">
163
163
  <button
164
164
  class="rounded-md px-2.5 py-1 transition"
165
165
  :class="
@@ -304,7 +304,7 @@ function exportJson() {
304
304
  :class="!c.ok ? 'border-rose-900/60' : ''"
305
305
  >
306
306
  <button
307
- class="flex w-full items-center gap-3 px-4 py-2.5 text-left transition hover:bg-slate-900/70"
307
+ class="flex w-full items-center gap-3 px-4 py-2.5 text-start transition hover:bg-slate-900/70"
308
308
  @click="toggle(c)"
309
309
  >
310
310
  <UIcon
@@ -325,7 +325,7 @@ function exportJson() {
325
325
  {{ c.provider }}:{{ c.model }}
326
326
  </span>
327
327
  <div
328
- class="ml-auto flex items-center gap-2.5 text-[11px] tabular-nums text-slate-400"
328
+ class="ms-auto flex items-center gap-2.5 text-[11px] tabular-nums text-slate-400"
329
329
  >
330
330
  <span
331
331
  :title="
@@ -456,7 +456,7 @@ function exportJson() {
456
456
  class="overflow-hidden rounded-xl border border-slate-800 bg-slate-900/40"
457
457
  >
458
458
  <button
459
- class="flex w-full items-center gap-3 px-4 py-2.5 text-left transition hover:bg-slate-900/70"
459
+ class="flex w-full items-center gap-3 px-4 py-2.5 text-start transition hover:bg-slate-900/70"
460
460
  @click="toggleCtx(s)"
461
461
  >
462
462
  <UIcon
@@ -474,7 +474,7 @@ function exportJson() {
474
474
  {{ s.model }}
475
475
  </span>
476
476
  <div
477
- class="ml-auto flex items-center gap-2.5 text-[11px] tabular-nums text-slate-400"
477
+ class="ms-auto flex items-center gap-2.5 text-[11px] tabular-nums text-slate-400"
478
478
  >
479
479
  <span :title="t('observability.context.injectedFiles')">{{
480
480
  t('observability.context.filesCount', { count: s.contextFiles.length })
@@ -275,7 +275,7 @@ async function copyRunId() {
275
275
  <span :class="v.passed ? 'text-emerald-300' : 'text-amber-300'">
276
276
  {{ pctOf(v.rating) }} {{ v.passed ? '≥' : '<' }} {{ pctOf(v.threshold) }}
277
277
  </span>
278
- <span v-if="v.feedback" class="ml-1 text-slate-400">— {{ v.feedback }}</span>
278
+ <span v-if="v.feedback" class="ms-1 text-slate-400">— {{ v.feedback }}</span>
279
279
  </div>
280
280
  </li>
281
281
  </ol>
@@ -35,7 +35,7 @@ function addTask() {
35
35
  >
36
36
  <UIcon name="i-lucide-package" class="h-3.5 w-3.5 text-violet-400" />
37
37
  <span class="truncate text-xs text-slate-200">{{ m.title }}</span>
38
- <span class="ml-auto text-[10px] text-slate-500">{{
38
+ <span class="ms-auto text-[10px] text-slate-500">{{
39
39
  t(
40
40
  'inspector.container.taskCount',
41
41
  { count: board.tasksOf(m.id).length },
@@ -71,7 +71,7 @@ function addTask() {
71
71
  :style="{ backgroundColor: STATUS_META[task.status].color }"
72
72
  />
73
73
  <span class="truncate text-xs text-slate-200">{{ task.title }}</span>
74
- <span class="ml-auto text-[10px] text-slate-500">{{
74
+ <span class="ms-auto text-[10px] text-slate-500">{{
75
75
  STATUS_META[task.status].label
76
76
  }}</span>
77
77
  </li>
@@ -69,7 +69,7 @@ const groups = computed(() => {
69
69
  <UIcon name="i-lucide-box" class="h-3 w-3 text-slate-500" />
70
70
  {{ group.service?.title ?? t('inspector.epicChildren.unassigned') }}
71
71
  </div>
72
- <div v-for="(mod, mi) in [...group.modules.values()]" :key="mi" class="pl-1">
72
+ <div v-for="(mod, mi) in [...group.modules.values()]" :key="mi" class="ps-1">
73
73
  <div v-if="mod.module" class="text-[10px] uppercase tracking-wide text-slate-500">
74
74
  {{ mod.module.title }}
75
75
  </div>
@@ -77,7 +77,7 @@ const groups = computed(() => {
77
77
  v-for="task in mod.tasks"
78
78
  :key="task.id"
79
79
  type="button"
80
- class="flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left text-xs text-slate-200 hover:bg-slate-800"
80
+ class="flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-start text-xs text-slate-200 hover:bg-slate-800"
81
81
  @click="ui.select(task.id)"
82
82
  >
83
83
  <span
@@ -85,7 +85,7 @@ const groups = computed(() => {
85
85
  :style="{ backgroundColor: STATUS_META[task.status].color }"
86
86
  />
87
87
  <span class="truncate">{{ task.title }}</span>
88
- <span class="ml-auto shrink-0 text-[10px] text-slate-500">
88
+ <span class="ms-auto shrink-0 text-[10px] text-slate-500">
89
89
  {{ STATUS_META[task.status].label }}
90
90
  </span>
91
91
  </button>
@@ -212,7 +212,7 @@ function fmtTime(ms: number) {
212
212
  {{ runStatusLabel(run.status) }}
213
213
  </span>
214
214
  <span class="truncate text-slate-500">{{ fmtTime(run.startedAt) }}</span>
215
- <span v-if="run.outcome" class="ml-auto truncate text-slate-500">{{ run.outcome }}</span>
215
+ <span v-if="run.outcome" class="ms-auto truncate text-slate-500">{{ run.outcome }}</span>
216
216
  </div>
217
217
  </div>
218
218
  </div>
@@ -99,7 +99,7 @@ function removeFragment(id: string) {
99
99
  :title="f.summary"
100
100
  @click="removeFragment(f.id)"
101
101
  >
102
- {{ f.title }}<UIcon name="i-lucide-x" class="ml-0.5 h-3 w-3" />
102
+ {{ f.title }}<UIcon name="i-lucide-x" class="ms-0.5 h-3 w-3" />
103
103
  </UBadge>
104
104
  </div>
105
105
  <div v-else class="text-[11px] text-slate-500">
@@ -222,7 +222,7 @@ const missingInfra = computed(() => !noInfra.value && composePath.value.trim() =
222
222
  <div class="border-t border-slate-800 pt-2">
223
223
  <button
224
224
  type="button"
225
- class="flex w-full items-center gap-1.5 text-left text-[11px] font-semibold uppercase tracking-wide text-slate-500 hover:text-slate-300"
225
+ class="flex w-full items-center gap-1.5 text-start text-[11px] font-semibold uppercase tracking-wide text-slate-500 hover:text-slate-300"
226
226
  @click="showProvisioning = !showProvisioning"
227
227
  >
228
228
  <UIcon
@@ -64,7 +64,7 @@ function removeDep(depId: string) {
64
64
  @click="removeDep(d.id)"
65
65
  >
66
66
  {{ label(d) }}
67
- <UIcon name="i-lucide-x" class="ml-0.5 h-3 w-3" />
67
+ <UIcon name="i-lucide-x" class="ms-0.5 h-3 w-3" />
68
68
  </UBadge>
69
69
  </div>
70
70
  <div v-else class="text-[11px] text-slate-500">{{ t('inspector.dependencies.empty') }}</div>
@@ -45,7 +45,7 @@ function barClass(n: number): string {
45
45
  :style="{ width: `${Math.round(estimate[axis.key] * 100)}%` }"
46
46
  />
47
47
  </div>
48
- <span class="w-9 shrink-0 text-right text-xs tabular-nums text-slate-300">{{
48
+ <span class="w-9 shrink-0 text-end text-xs tabular-nums text-slate-300">{{
49
49
  n(estimate[axis.key], { key: 'percent' })
50
50
  }}</span>
51
51
  </div>