@cat-factory/app 0.54.4 → 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 +1 -1
@@ -190,7 +190,7 @@ async function resetRun() {
190
190
  model, subtasks + the prose output when there is one). -->
191
191
  <button
192
192
  type="button"
193
- class="flex min-w-0 cursor-pointer items-center gap-2 text-left transition hover:text-white"
193
+ class="flex min-w-0 cursor-pointer items-center gap-2 text-start transition hover:text-white"
194
194
  :title="
195
195
  s.output
196
196
  ? t('inspector.execution.viewDetailsOutput')
@@ -220,7 +220,7 @@ async function resetRun() {
220
220
  </button>
221
221
  <span
222
222
  v-if="s.subtasks && s.subtasks.total > 0"
223
- class="ml-auto font-mono text-[10px] tabular-nums text-slate-300"
223
+ class="ms-auto font-mono text-[10px] tabular-nums text-slate-300"
224
224
  :title="
225
225
  s.subtasks.inProgress > 0
226
226
  ? t('inspector.execution.subtasksProgress', {
@@ -240,7 +240,7 @@ async function resetRun() {
240
240
  class="inline-flex items-center gap-1 text-[10px]"
241
241
  :class="[
242
242
  stepFailed(s) ? 'text-rose-400' : 'text-slate-400',
243
- { 'ml-auto': !s.subtasks },
243
+ { 'ms-auto': !s.subtasks },
244
244
  ]"
245
245
  >
246
246
  <UIcon v-if="stepFailed(s)" name="i-lucide-circle-x" class="h-3 w-3 shrink-0" />
@@ -300,7 +300,7 @@ async function resetRun() {
300
300
  </div>
301
301
  <div
302
302
  v-if="s.subtasks && s.subtasks.total > 0"
303
- class="mt-1 ml-6 h-1 overflow-hidden rounded-full bg-slate-700/60"
303
+ class="mt-1 ms-6 h-1 overflow-hidden rounded-full bg-slate-700/60"
304
304
  >
305
305
  <div
306
306
  class="h-full rounded-full bg-indigo-400 transition-all duration-500"
@@ -309,7 +309,7 @@ async function resetRun() {
309
309
  </div>
310
310
  <div
311
311
  v-if="s.model"
312
- class="mt-0.5 flex items-center gap-1 pl-6 text-[10px] text-slate-500"
312
+ class="mt-0.5 flex items-center gap-1 ps-6 text-[10px] text-slate-500"
313
313
  :title="s.model"
314
314
  >
315
315
  <UIcon name="i-lucide-cpu" class="h-3 w-3" />
@@ -318,7 +318,7 @@ async function resetRun() {
318
318
  <!-- Prompt-fragment standards the library selected for this step. -->
319
319
  <div
320
320
  v-if="s.selectedFragmentIds && s.selectedFragmentIds.length"
321
- class="mt-0.5 flex flex-wrap items-center gap-1 pl-6 text-[10px] text-slate-500"
321
+ class="mt-0.5 flex flex-wrap items-center gap-1 ps-6 text-[10px] text-slate-500"
322
322
  :title="
323
323
  t('inspector.execution.fragmentsTooltip', {
324
324
  fragments: s.selectedFragmentIds.join(', '),
@@ -338,7 +338,7 @@ async function resetRun() {
338
338
  completed/skipped, so it's clear whether a fix pass ran. -->
339
339
  <div
340
340
  v-if="gateCompanionFor(s, runFailed)"
341
- class="mt-0.5 flex items-center gap-1.5 pl-6 text-[10px]"
341
+ class="mt-0.5 flex items-center gap-1.5 ps-6 text-[10px]"
342
342
  >
343
343
  <UIcon
344
344
  :name="agentKindMeta(gateCompanionFor(s, runFailed)!.kind).icon"
@@ -356,7 +356,7 @@ async function resetRun() {
356
356
  }}
357
357
  </span>
358
358
  <span
359
- class="ml-auto"
359
+ class="ms-auto"
360
360
  :class="COMPANION_STATE_META[gateCompanionFor(s, runFailed)!.state].text"
361
361
  >
362
362
  {{ COMPANION_STATE_META[gateCompanionFor(s, runFailed)!.state].label }}
@@ -388,7 +388,7 @@ async function resetRun() {
388
388
  >
389
389
  <span class="flex w-full items-center gap-2">
390
390
  {{ prLabel }}
391
- <UBadge :color="prMerged ? 'success' : 'info'" variant="subtle" size="sm" class="ml-auto">
391
+ <UBadge :color="prMerged ? 'success' : 'info'" variant="subtle" size="sm" class="ms-auto">
392
392
  {{ prMerged ? t('inspector.execution.merged') : t('inspector.execution.open') }}
393
393
  </UBadge>
394
394
  </span>
@@ -246,7 +246,7 @@ const technicalLabel = computed(() => {
246
246
  class="cursor-pointer"
247
247
  @click="setPipeline('')"
248
248
  >
249
- {{ selectedPipeline.name }}<UIcon name="i-lucide-x" class="ml-0.5 h-3 w-3" />
249
+ {{ selectedPipeline.name }}<UIcon name="i-lucide-x" class="ms-0.5 h-3 w-3" />
250
250
  </UBadge>
251
251
  </div>
252
252
  <div v-else class="text-[11px] text-slate-500">
@@ -461,7 +461,7 @@ const technicalLabel = computed(() => {
461
461
  class="cursor-pointer"
462
462
  @click="setResponsible('')"
463
463
  >
464
- {{ responsibleLabel }}<UIcon name="i-lucide-x" class="ml-0.5 h-3 w-3" />
464
+ {{ responsibleLabel }}<UIcon name="i-lucide-x" class="ms-0.5 h-3 w-3" />
465
465
  </UBadge>
466
466
  </div>
467
467
  <div v-else class="text-[11px] text-slate-500">
@@ -112,7 +112,7 @@ function removeFragment(id: string) {
112
112
  :title="f.summary"
113
113
  @click="removeFragment(f.id)"
114
114
  >
115
- {{ f.title }}<UIcon name="i-lucide-x" class="ml-0.5 h-3 w-3" />
115
+ {{ f.title }}<UIcon name="i-lucide-x" class="ms-0.5 h-3 w-3" />
116
116
  </UBadge>
117
117
  </div>
118
118
  <div v-else class="text-[11px] text-slate-500">
@@ -53,6 +53,7 @@ const emit = defineEmits<{ resolve: [choice: IterationCapChoice] }>()
53
53
  variant="soft"
54
54
  size="xs"
55
55
  icon="i-lucide-arrow-right"
56
+ :ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
56
57
  :loading="loading"
57
58
  @click="emit('resolve', 'proceed')"
58
59
  >
@@ -241,7 +241,7 @@ async function clone(p: Pipeline) {
241
241
  {{ t('pipeline.builder.addAgent') }}
242
242
  </UButton>
243
243
  </div>
244
- <div class="flex-1 pr-1 lg:min-h-0 lg:overflow-y-auto">
244
+ <div class="flex-1 pe-1 lg:min-h-0 lg:overflow-y-auto">
245
245
  <AgentPalette @add="add" />
246
246
  </div>
247
247
  </div>
@@ -309,7 +309,7 @@ async function clone(p: Pipeline) {
309
309
  {{ t('pipeline.builder.emptyDraft') }}
310
310
  </div>
311
311
 
312
- <ol v-else class="flex-1 space-y-2 pr-1 lg:min-h-0 lg:overflow-y-auto">
312
+ <ol v-else class="flex-1 space-y-2 pe-1 lg:min-h-0 lg:overflow-y-auto">
313
313
  <li
314
314
  v-for="(unit, vi) in pipelines.units"
315
315
  :key="unit.index"
@@ -455,7 +455,7 @@ async function clone(p: Pipeline) {
455
455
  gated on the task estimate. -->
456
456
  <div
457
457
  v-if="unit.companionIndex !== null"
458
- class="ml-6 space-y-2 rounded-md border border-fuchsia-800/40 bg-fuchsia-950/20 p-2 text-xs"
458
+ class="ms-6 space-y-2 rounded-md border border-fuchsia-800/40 bg-fuchsia-950/20 p-2 text-xs"
459
459
  >
460
460
  <div class="flex items-center gap-1.5">
461
461
  <UIcon name="i-lucide-corner-down-right" class="h-3.5 w-3.5 text-slate-500" />
@@ -524,7 +524,7 @@ async function clone(p: Pipeline) {
524
524
  <!-- Consensus config (shown when the step is consensus-enabled). -->
525
525
  <div
526
526
  v-if="pipelines.draftConsensus[unit.index]?.enabled"
527
- class="ml-6 space-y-2 rounded-md border border-emerald-800/40 bg-emerald-950/20 p-2 text-xs"
527
+ class="ms-6 space-y-2 rounded-md border border-emerald-800/40 bg-emerald-950/20 p-2 text-xs"
528
528
  >
529
529
  <div class="flex items-center gap-2">
530
530
  <label class="text-slate-400">{{ t('pipeline.builder.strategy') }}</label>
@@ -538,7 +538,7 @@ async function clone(p: Pipeline) {
538
538
  </select>
539
539
  <label
540
540
  v-if="pipelines.draftConsensus[unit.index]!.strategy === 'debate'"
541
- class="ml-2 text-slate-400"
541
+ class="ms-2 text-slate-400"
542
542
  >{{ t('pipeline.builder.rounds') }}</label
543
543
  >
544
544
  <input
@@ -681,7 +681,7 @@ async function clone(p: Pipeline) {
681
681
  </UBadge>
682
682
  </div>
683
683
 
684
- <ul class="flex-1 space-y-1.5 pr-1 lg:min-h-0 lg:overflow-y-auto">
684
+ <ul class="flex-1 space-y-1.5 pe-1 lg:min-h-0 lg:overflow-y-auto">
685
685
  <li
686
686
  v-for="p in visiblePipelines"
687
687
  :key="p.id"
@@ -691,7 +691,7 @@ async function clone(p: Pipeline) {
691
691
  <div class="flex items-center gap-2 px-2 py-1.5">
692
692
  <button
693
693
  type="button"
694
- class="flex min-w-0 flex-1 items-center gap-2 text-left"
694
+ class="flex min-w-0 flex-1 items-center gap-2 text-start"
695
695
  @click="toggleSaved(p.id)"
696
696
  >
697
697
  <UIcon
@@ -784,7 +784,7 @@ async function clone(p: Pipeline) {
784
784
  <!-- Full ordered step list, revealed on click. -->
785
785
  <ol
786
786
  v-if="expandedSaved.has(p.id)"
787
- class="space-y-1 border-t border-slate-800 px-2 py-2 pl-7"
787
+ class="space-y-1 border-t border-slate-800 px-2 py-2 ps-7"
788
788
  >
789
789
  <li
790
790
  v-for="(k, i) in p.agentKinds"
@@ -213,7 +213,7 @@ const ITEM_ICON: Record<string, string> = {
213
213
  <span v-if="currentAgent && instance.status === 'running'" class="text-xs text-slate-500">
214
214
  · {{ t('pipeline.progress.currently', { agent: currentAgent }) }}
215
215
  </span>
216
- <span class="ml-auto font-mono text-sm tabular-nums text-slate-200">{{
216
+ <span class="ms-auto font-mono text-sm tabular-nums text-slate-200">{{
217
217
  t('pipeline.progress.percent', { value: overallPct })
218
218
  }}</span>
219
219
  </div>
@@ -241,7 +241,7 @@ const ITEM_ICON: Record<string, string> = {
241
241
  <!-- connector line to the next step -->
242
242
  <span
243
243
  v-if="i < steps.length - 1"
244
- class="absolute top-9 bottom-0 left-[17px] w-0.5 -translate-x-1/2"
244
+ class="absolute top-9 bottom-0 start-[17px] w-0.5 -translate-x-1/2"
245
245
  :class="connectorDone(i) ? 'bg-emerald-500/60' : 'bg-slate-700'"
246
246
  />
247
247
 
@@ -306,7 +306,7 @@ const ITEM_ICON: Record<string, string> = {
306
306
  </div>
307
307
  </div>
308
308
  <span
309
- class="ml-auto shrink-0 text-[11px] font-medium"
309
+ class="ms-auto shrink-0 text-[11px] font-medium"
310
310
  :style="{ color: stepVisual(s.state).color }"
311
311
  >
312
312
  {{ stepVisual(s.state).label }}
@@ -495,7 +495,7 @@ const ITEM_ICON: Record<string, string> = {
495
495
  <button
496
496
  v-if="s.followUps?.enabled"
497
497
  type="button"
498
- class="mt-3 flex w-full items-center gap-2 rounded-lg border border-dashed px-2.5 py-1.5 text-left transition hover:border-pink-400/60"
498
+ class="mt-3 flex w-full items-center gap-2 rounded-lg border border-dashed px-2.5 py-1.5 text-start transition hover:border-pink-400/60"
499
499
  :class="
500
500
  followUpPending(s) > 0
501
501
  ? 'border-pink-500/50 bg-pink-500/10 followup-blink'
@@ -287,7 +287,7 @@ async function remove(k: ApiKey) {
287
287
 
288
288
  <!-- where to get the key -->
289
289
  <ol
290
- class="list-decimal space-y-1.5 rounded-lg border border-slate-700 bg-slate-900/60 p-4 pl-8 text-sm text-slate-300"
290
+ class="list-decimal space-y-1.5 rounded-lg border border-slate-700 bg-slate-900/60 p-4 ps-8 text-sm text-slate-300"
291
291
  >
292
292
  <li v-for="(step, i) in selected.steps" :key="i">{{ step }}</li>
293
293
  <li>
@@ -351,7 +351,7 @@ async function remove(k: ApiKey) {
351
351
  >
352
352
  <div>
353
353
  <span class="font-medium text-slate-200">{{ k.label }}</span>
354
- <span class="ml-2 text-xs text-slate-500">{{ providerLabel(k.provider) }}</span>
354
+ <span class="ms-2 text-xs text-slate-500">{{ providerLabel(k.provider) }}</span>
355
355
  <div class="text-[11px] tabular-nums text-slate-500">
356
356
  {{
357
357
  t(
@@ -179,7 +179,7 @@ async function disconnect(v: SubscriptionVendor) {
179
179
  >
180
180
  <div>
181
181
  <span class="font-medium text-slate-200">{{ sub.label }}</span>
182
- <span class="ml-2 text-xs text-slate-500">{{ vendorLabel(sub.vendor) }}</span>
182
+ <span class="ms-2 text-xs text-slate-500">{{ vendorLabel(sub.vendor) }}</span>
183
183
  <div class="text-[11px] text-slate-500">
184
184
  <template v-if="sub.expiresAt">
185
185
  {{ t('personalSubscriptions.expires', { date: d(new Date(sub.expiresAt), 'short') }) }}
@@ -210,7 +210,7 @@ async function disconnect(v: SubscriptionVendor) {
210
210
 
211
211
  <!-- connect / replace form -->
212
212
  <ol
213
- class="list-decimal space-y-1.5 rounded-lg border border-slate-700 bg-slate-900/60 p-4 pl-8 text-sm text-slate-300"
213
+ class="list-decimal space-y-1.5 rounded-lg border border-slate-700 bg-slate-900/60 p-4 ps-8 text-sm text-slate-300"
214
214
  >
215
215
  <li v-for="(step, i) in selectedMeta.steps" :key="i">{{ step }}</li>
216
216
  </ol>
@@ -192,7 +192,7 @@ function vendorLabel(v: SubscriptionVendor): string {
192
192
 
193
193
  <!-- guided steps -->
194
194
  <ol
195
- class="list-decimal space-y-1.5 rounded-lg border border-slate-700 bg-slate-900/60 p-4 pl-8 text-sm text-slate-300"
195
+ class="list-decimal space-y-1.5 rounded-lg border border-slate-700 bg-slate-900/60 p-4 ps-8 text-sm text-slate-300"
196
196
  >
197
197
  <li v-for="(step, i) in steps" :key="i">{{ step }}</li>
198
198
  </ol>
@@ -239,7 +239,7 @@ function vendorLabel(v: SubscriptionVendor): string {
239
239
  >
240
240
  <div>
241
241
  <span class="font-medium text-slate-200">{{ c.label }}</span>
242
- <span class="ml-2 text-xs text-slate-500">{{ vendorLabel(c.vendor) }}</span>
242
+ <span class="ms-2 text-xs text-slate-500">{{ vendorLabel(c.vendor) }}</span>
243
243
  <div class="text-[11px] tabular-nums text-slate-500">
244
244
  {{
245
245
  t(
@@ -94,7 +94,7 @@ function when(epochMs: number): string {
94
94
  "
95
95
  >{{ OUTCOME_LABEL[entry.outcome] }}</span
96
96
  >
97
- <span class="ml-auto text-[11px] text-slate-500">{{ when(entry.createdAt) }}</span>
97
+ <span class="ms-auto text-[11px] text-slate-500">{{ when(entry.createdAt) }}</span>
98
98
  </div>
99
99
  <div v-if="entry.targetId" class="mt-0.5 text-[11px] text-slate-500">
100
100
  {{ entry.providerId ? `${entry.providerId} · ` : '' }}{{ entry.targetId }}
@@ -428,7 +428,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
428
428
  </h1>
429
429
  <p v-if="block" class="truncate text-xs text-slate-500">{{ block.title }}</p>
430
430
  </div>
431
- <div class="ml-auto flex items-center gap-1.5">
431
+ <div class="ms-auto flex items-center gap-1.5">
432
432
  <UBadge v-if="review" color="neutral" variant="subtle" size="sm">
433
433
  {{ t('requirements.iteration', { current: iteration, max: maxIterations }) }}
434
434
  </UBadge>
@@ -538,7 +538,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
538
538
  size="xs"
539
539
  variant="soft"
540
540
  :color="STATUS_COLOR[item.status]"
541
- class="ml-auto"
541
+ class="ms-auto"
542
542
  >
543
543
  {{ STATUS_LABELS[item.status] }}
544
544
  </UBadge>
@@ -551,7 +551,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
551
551
  ones the answer lives in the textarea below, seeded from the reply) -->
552
552
  <div
553
553
  v-if="item.reply && item.status !== 'open' && item.status !== 'answered'"
554
- class="mt-2 rounded-md border-l-2 border-slate-700 bg-slate-950/40 px-3 py-1.5 text-sm text-slate-300"
554
+ class="mt-2 rounded-md border-s-2 border-slate-700 bg-slate-950/40 px-3 py-1.5 text-sm text-slate-300"
555
555
  >
556
556
  <span class="text-[10px] uppercase tracking-wide text-slate-500">
557
557
  {{ t('requirements.answerLabel') }}
@@ -641,7 +641,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
641
641
  }}</span>
642
642
  <span
643
643
  v-if="recommendationProgress"
644
- class="ml-auto flex items-center gap-1.5 normal-case text-indigo-300/80"
644
+ class="ms-auto flex items-center gap-1.5 normal-case text-indigo-300/80"
645
645
  >
646
646
  <UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 animate-spin" />
647
647
  {{
@@ -764,7 +764,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
764
764
  <div v-for="s in outline.sections" :key="s.id" class="mb-2">
765
765
  <button
766
766
  v-if="s.title"
767
- class="group flex w-full items-center gap-2 text-left"
767
+ class="group flex w-full items-center gap-2 text-start"
768
768
  @click="toggle(s.id)"
769
769
  >
770
770
  <UIcon
@@ -780,7 +780,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
780
780
  </button>
781
781
  <div
782
782
  v-show="!s.title || !collapsed[s.id]"
783
- class="reader-prose mt-1 pl-5.5 text-[13px] leading-relaxed text-slate-300"
783
+ class="reader-prose mt-1 ps-5.5 text-[13px] leading-relaxed text-slate-300"
784
784
  v-html="s.bodyHtml"
785
785
  />
786
786
  </div>
@@ -789,7 +789,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
789
789
  </div>
790
790
 
791
791
  <!-- right action rail -->
792
- <aside class="hidden w-72 shrink-0 flex-col border-l border-slate-800 lg:flex">
792
+ <aside class="hidden w-72 shrink-0 flex-col border-s border-slate-800 lg:flex">
793
793
  <div class="flex flex-col gap-4 px-4 py-5">
794
794
  <div v-if="review" class="space-y-2 text-xs text-slate-400">
795
795
  <div class="flex items-center justify-between">
@@ -806,7 +806,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
806
806
  </div>
807
807
  <div v-if="review.model" class="flex items-center justify-between">
808
808
  <span>{{ t('requirements.stats.model') }}</span>
809
- <span class="truncate pl-2 text-slate-500">{{ review.model }}</span>
809
+ <span class="truncate ps-2 text-slate-500">{{ review.model }}</span>
810
810
  </div>
811
811
  </div>
812
812
 
@@ -821,6 +821,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
821
821
  size="sm"
822
822
  block
823
823
  icon="i-lucide-arrow-right"
824
+ :ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
824
825
  :loading="acting"
825
826
  @click="proceed"
826
827
  >
@@ -279,7 +279,7 @@ async function archive(prompt: SandboxPromptVersion) {
279
279
  <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
280
280
  {{ t('sandbox.builder.promptVersions') }}
281
281
  </span>
282
- <div class="max-h-28 space-y-1 overflow-auto pr-1">
282
+ <div class="max-h-28 space-y-1 overflow-auto pe-1">
283
283
  <label
284
284
  v-for="p in kindPrompts"
285
285
  :key="p.id"
@@ -311,7 +311,7 @@ async function archive(prompt: SandboxPromptVersion) {
311
311
  <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
312
312
  {{ t('sandbox.builder.models') }}
313
313
  </span>
314
- <div class="max-h-28 space-y-1 overflow-auto pr-1">
314
+ <div class="max-h-28 space-y-1 overflow-auto pe-1">
315
315
  <label
316
316
  v-for="m in store.selectableModels"
317
317
  :key="m.id"
@@ -335,7 +335,7 @@ async function archive(prompt: SandboxPromptVersion) {
335
335
  <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
336
336
  {{ t('sandbox.builder.fixtures') }}
337
337
  </span>
338
- <div class="max-h-28 space-y-1 overflow-auto pr-1">
338
+ <div class="max-h-28 space-y-1 overflow-auto pe-1">
339
339
  <label
340
340
  v-for="f in kindFixtures"
341
341
  :key="f.id"
@@ -401,13 +401,13 @@ async function archive(prompt: SandboxPromptVersion) {
401
401
  }}</UBadge>
402
402
  </div>
403
403
  <div class="overflow-auto">
404
- <table class="w-full text-left text-xs">
404
+ <table class="w-full text-start text-xs">
405
405
  <thead class="text-slate-500">
406
406
  <tr>
407
- <th class="py-1 pr-2 font-medium">{{ t('sandbox.results.col.prompt') }}</th>
408
- <th class="py-1 pr-2 font-medium">{{ t('sandbox.results.col.model') }}</th>
409
- <th class="py-1 pr-2 font-medium">{{ t('sandbox.results.col.fixture') }}</th>
410
- <th class="py-1 pr-2 font-medium">{{ t('sandbox.results.col.score') }}</th>
407
+ <th class="py-1 pe-2 font-medium">{{ t('sandbox.results.col.prompt') }}</th>
408
+ <th class="py-1 pe-2 font-medium">{{ t('sandbox.results.col.model') }}</th>
409
+ <th class="py-1 pe-2 font-medium">{{ t('sandbox.results.col.fixture') }}</th>
410
+ <th class="py-1 pe-2 font-medium">{{ t('sandbox.results.col.score') }}</th>
411
411
  <th class="py-1 font-medium">{{ t('sandbox.results.col.objective') }}</th>
412
412
  </tr>
413
413
  </thead>
@@ -418,12 +418,12 @@ async function archive(prompt: SandboxPromptVersion) {
418
418
  class="cursor-pointer border-t border-slate-800 hover:bg-slate-800/40"
419
419
  @click="selectedRun = run"
420
420
  >
421
- <td class="py-1 pr-2 text-slate-300">{{ run.promptLabel }}</td>
422
- <td class="py-1 pr-2 font-mono text-[11px] text-slate-400">
421
+ <td class="py-1 pe-2 text-slate-300">{{ run.promptLabel }}</td>
422
+ <td class="py-1 pe-2 font-mono text-[11px] text-slate-400">
423
423
  {{ run.model }}
424
424
  </td>
425
- <td class="py-1 pr-2 text-slate-400">{{ fixtureName }}</td>
426
- <td class="py-1 pr-2">
425
+ <td class="py-1 pe-2 text-slate-400">{{ fixtureName }}</td>
426
+ <td class="py-1 pe-2">
427
427
  <span
428
428
  v-if="grade"
429
429
  :class="scoreColor(grade.weightedTotal)"
@@ -470,8 +470,8 @@ async function archive(prompt: SandboxPromptVersion) {
470
470
  class="text-[11px] text-slate-400"
471
471
  >
472
472
  <span :class="scoreColor(d.score)" class="font-semibold">{{ d.score }}</span>
473
- <span class="ml-1 text-slate-300">{{ d.key }}</span>
474
- <span v-if="d.rationale" class="ml-1 text-slate-500">— {{ d.rationale }}</span>
473
+ <span class="ms-1 text-slate-300">{{ d.key }}</span>
474
+ <span v-if="d.rationale" class="ms-1 text-slate-500">— {{ d.rationale }}</span>
475
475
  </p>
476
476
  </div>
477
477
  </div>
@@ -484,7 +484,7 @@ async function archive(prompt: SandboxPromptVersion) {
484
484
  <button
485
485
  v-for="x in store.experiments"
486
486
  :key="x.id"
487
- class="flex w-full items-center justify-between rounded-md border border-slate-800 bg-slate-900/40 px-2 py-1.5 text-left text-sm hover:bg-slate-800/50"
487
+ class="flex w-full items-center justify-between rounded-md border border-slate-800 bg-slate-900/40 px-2 py-1.5 text-start text-sm hover:bg-slate-800/50"
488
488
  @click="store.openExperiment(x.id)"
489
489
  >
490
490
  <span class="truncate text-slate-300">{{ x.name }}</span>
@@ -499,7 +499,7 @@ async function archive(prompt: SandboxPromptVersion) {
499
499
 
500
500
  <!-- ============================== PROMPTS ============================== -->
501
501
  <div v-else-if="tab === 'prompts'" class="grid gap-4 lg:grid-cols-2">
502
- <div class="max-h-[28rem] space-y-1.5 overflow-auto pr-1">
502
+ <div class="max-h-[28rem] space-y-1.5 overflow-auto pe-1">
503
503
  <div
504
504
  v-for="p in store.prompts"
505
505
  :key="p.id"
@@ -581,7 +581,7 @@ async function archive(prompt: SandboxPromptVersion) {
581
581
  </div>
582
582
 
583
583
  <!-- ============================== FIXTURES ============================== -->
584
- <div v-else class="max-h-[28rem] space-y-1.5 overflow-auto pr-1">
584
+ <div v-else class="max-h-[28rem] space-y-1.5 overflow-auto pe-1">
585
585
  <div
586
586
  v-for="f in store.fixtures"
587
587
  :key="f.id"
@@ -191,7 +191,7 @@ async function remove(p: LocalRunner) {
191
191
  >
192
192
  <div>
193
193
  <span class="font-medium text-slate-200">{{ e.label }}</span>
194
- <span class="ml-2 text-xs text-slate-500">{{ LOCAL_RUNNER_LABELS[e.provider] }}</span>
194
+ <span class="ms-2 text-xs text-slate-500">{{ LOCAL_RUNNER_LABELS[e.provider] }}</span>
195
195
  <div class="text-[11px] text-slate-500">
196
196
  {{ e.baseUrl }} ·
197
197
  {{
@@ -267,10 +267,11 @@ function fail(title: string, e: unknown) {
267
267
  <UButton
268
268
  v-if="editor"
269
269
  icon="i-lucide-arrow-left"
270
+ :ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
270
271
  color="neutral"
271
272
  variant="ghost"
272
273
  size="sm"
273
- class="ml-auto"
274
+ class="ms-auto"
274
275
  @click="editor = null"
275
276
  >
276
277
  {{ t('settings.modelConfiguration.back') }}
@@ -280,7 +281,7 @@ function fail(title: string, e: unknown) {
280
281
  color="neutral"
281
282
  variant="ghost"
282
283
  size="sm"
283
- :class="editor ? '' : 'ml-auto'"
284
+ :class="editor ? '' : 'ms-auto'"
284
285
  :title="t('settings.modelConfiguration.closeEsc')"
285
286
  @click="open = false"
286
287
  />
@@ -330,7 +331,7 @@ function fail(title: string, e: unknown) {
330
331
  <UBadge v-if="p.isDefault" color="primary" variant="subtle" size="xs">
331
332
  {{ t('settings.modelConfiguration.list.default') }}
332
333
  </UBadge>
333
- <div class="ml-auto flex items-center gap-1">
334
+ <div class="ms-auto flex items-center gap-1">
334
335
  <UButton
335
336
  v-if="!p.isDefault"
336
337
  size="xs"
@@ -235,7 +235,7 @@ function manageKeys() {
235
235
  <h4 class="text-xs font-semibold uppercase tracking-wide text-slate-500">
236
236
  {{ t('settings.openRouterCatalog.connectHeading') }}
237
237
  </h4>
238
- <ol class="list-decimal space-y-1 pl-5 text-sm text-slate-300">
238
+ <ol class="list-decimal space-y-1 ps-5 text-sm text-slate-300">
239
239
  <li>
240
240
  <i18n-t keypath="settings.openRouterCatalog.step1" tag="span" scope="global">
241
241
  <template #link>
@@ -343,7 +343,7 @@ function manageKeys() {
343
343
 
344
344
  <p v-if="store.refreshError" class="text-xs text-rose-400">{{ store.refreshError }}</p>
345
345
 
346
- <div v-if="visible.length" class="max-h-96 space-y-1 overflow-y-auto pr-1">
346
+ <div v-if="visible.length" class="max-h-96 space-y-1 overflow-y-auto pe-1">
347
347
  <label
348
348
  v-for="m in visible"
349
349
  :key="m.id"
@@ -357,7 +357,7 @@ function manageKeys() {
357
357
  <span class="block truncate text-slate-200">{{ m.name }}</span>
358
358
  <span class="block truncate font-mono text-[11px] text-slate-500">{{ m.id }}</span>
359
359
  </span>
360
- <span class="shrink-0 text-right text-[11px] text-slate-500">
360
+ <span class="shrink-0 text-end text-[11px] text-slate-500">
361
361
  <span v-if="m.contextLength" class="block">{{
362
362
  contextLabel(m.contextLength)
363
363
  }}</span>
@@ -97,7 +97,7 @@ function remove(id: string) {
97
97
  :title="f.summary"
98
98
  @click="remove(f.id)"
99
99
  >
100
- {{ f.title }}<UIcon name="i-lucide-x" class="ml-0.5 h-3 w-3" />
100
+ {{ f.title }}<UIcon name="i-lucide-x" class="ms-0.5 h-3 w-3" />
101
101
  </UBadge>
102
102
  </div>
103
103
  <p v-else class="text-[11px] text-slate-500">
@@ -137,7 +137,7 @@ function kindLabel(item: RequirementItem): string {
137
137
  {{ spec?.service || block.title }}
138
138
  </p>
139
139
  </div>
140
- <div class="ml-auto flex items-center gap-1.5">
140
+ <div class="ms-auto flex items-center gap-1.5">
141
141
  <!-- view toggle: Gherkin only when the spec (and its feature files) are on main -->
142
142
  <div v-if="present" class="flex items-center rounded-lg border border-slate-700 p-0.5">
143
143
  <UButton
@@ -200,7 +200,7 @@ function kindLabel(item: RequirementItem): string {
200
200
  <!-- spec body: navigable tree + detail -->
201
201
  <div v-else class="flex min-h-0 flex-1">
202
202
  <!-- nav: modules → feature groups -->
203
- <nav class="w-64 shrink-0 overflow-y-auto border-r border-slate-800 px-3 py-4">
203
+ <nav class="w-64 shrink-0 overflow-y-auto border-e border-slate-800 px-3 py-4">
204
204
  <UButton
205
205
  block
206
206
  class="mb-2 justify-start"
@@ -222,7 +222,7 @@ function kindLabel(item: RequirementItem): string {
222
222
  <li v-for="(group, gi) in mod.groups ?? []" :key="gi">
223
223
  <button
224
224
  type="button"
225
- class="flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-[13px] transition"
225
+ class="flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-start text-[13px] transition"
226
226
  :class="
227
227
  selected?.m === mi && selected?.g === gi
228
228
  ? 'bg-indigo-500/15 text-indigo-200'
@@ -203,12 +203,12 @@ onMounted(() => {
203
203
  v-for="row in importedRows"
204
204
  :key="`imp:${row.externalId}`"
205
205
  type="button"
206
- class="flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-xs text-slate-300 hover:bg-slate-800/70"
206
+ class="flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-start text-xs text-slate-300 hover:bg-slate-800/70"
207
207
  @click="pickImported(row)"
208
208
  >
209
209
  <UIcon :name="icon" class="h-3.5 w-3.5 shrink-0 text-indigo-400" />
210
210
  <span class="truncate">{{ row.externalId }} · {{ row.title }}</span>
211
- <UBadge color="neutral" variant="soft" size="xs" class="ml-auto shrink-0">{{
211
+ <UBadge color="neutral" variant="soft" size="xs" class="ms-auto shrink-0">{{
212
212
  t('tasks.picker.imported')
213
213
  }}</UBadge>
214
214
  </button>
@@ -218,12 +218,12 @@ onMounted(() => {
218
218
  v-for="r in searchRows"
219
219
  :key="`hit:${r.externalId}`"
220
220
  type="button"
221
- class="flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-xs text-slate-300 hover:bg-slate-800/70"
221
+ class="flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-start text-xs text-slate-300 hover:bg-slate-800/70"
222
222
  @click="pickSearch(r)"
223
223
  >
224
224
  <UIcon :name="icon" class="h-3.5 w-3.5 shrink-0 text-slate-400" />
225
225
  <span class="truncate">{{ r.externalId }} · {{ r.title }}</span>
226
- <UBadge v-if="r.status" color="neutral" variant="soft" size="xs" class="ml-auto shrink-0">
226
+ <UBadge v-if="r.status" color="neutral" variant="soft" size="xs" class="ms-auto shrink-0">
227
227
  {{ r.status }}
228
228
  </UBadge>
229
229
  </button>
@@ -232,7 +232,7 @@ onMounted(() => {
232
232
  <button
233
233
  v-if="refRow"
234
234
  type="button"
235
- class="flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-xs text-slate-300 hover:bg-slate-800/70"
235
+ class="flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-start text-xs text-slate-300 hover:bg-slate-800/70"
236
236
  @click="pickRef(refRow)"
237
237
  >
238
238
  <UIcon name="i-lucide-link" class="h-3.5 w-3.5 shrink-0 text-slate-400" />
@@ -79,7 +79,7 @@ const attachMenu = computed<DropdownMenuItem[][]>(() => {
79
79
  class="h-3.5 w-3.5 shrink-0 text-indigo-400"
80
80
  />
81
81
  <span class="truncate">{{ task.externalId }} · {{ task.title }}</span>
82
- <UBadge color="neutral" variant="soft" size="xs" class="ml-auto shrink-0">
82
+ <UBadge color="neutral" variant="soft" size="xs" class="ms-auto shrink-0">
83
83
  {{ task.status }}
84
84
  </UBadge>
85
85
  </a>