@cat-factory/app 0.138.1 → 0.140.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 (47) hide show
  1. package/app/components/board/AddTaskModal.vue +12 -39
  2. package/app/components/brainstorm/BrainstormWindow.vue +338 -374
  3. package/app/components/clarity/ClarityReviewWindow.vue +323 -353
  4. package/app/components/consensus/ConsensusSessionWindow.vue +138 -150
  5. package/app/components/docs/DocInterviewWindow.vue +108 -136
  6. package/app/components/documents/ContextDocumentPicker.vue +89 -70
  7. package/app/components/documents/RepoContextDocPicker.vue +277 -0
  8. package/app/components/followUp/FollowUpWindow.vue +3 -5
  9. package/app/components/forkDecision/ForkDecisionWindow.vue +1 -3
  10. package/app/components/gates/GateResultView.vue +3 -5
  11. package/app/components/github/RepoTreeBrowser.vue +19 -10
  12. package/app/components/humanTest/HumanTestWindow.vue +3 -5
  13. package/app/components/initiative/InitiativePlanningWindow.vue +89 -112
  14. package/app/components/initiative/InitiativeTrackerWindow.vue +389 -424
  15. package/app/components/panels/GenericStructuredResultView.vue +3 -5
  16. package/app/components/panels/MergerResultView.vue +3 -5
  17. package/app/components/panels/ResultWindowShell.vue +1 -1
  18. package/app/components/panels/inspector/TaskRunSettings.vue +16 -23
  19. package/app/components/pipeline/PipelineBuilder.vue +10 -0
  20. package/app/components/pipeline/PipelinePicker.vue +120 -0
  21. package/app/components/pipeline/PipelinePreview.vue +49 -0
  22. package/app/components/prReview/PrReviewWindow.vue +181 -205
  23. package/app/components/ralph/RalphLoopResultView.vue +3 -5
  24. package/app/components/requirements/RequirementsReviewWindow.vue +512 -557
  25. package/app/components/spec/ServiceSpecWindow.vue +235 -266
  26. package/app/components/testing/TestReportWindow.vue +4 -6
  27. package/app/components/visualConfirm/VisualConfirmationWindow.vue +3 -5
  28. package/app/composables/api/github.ts +8 -0
  29. package/app/composables/useContextLinking.spec.ts +138 -0
  30. package/app/composables/useContextLinking.ts +120 -8
  31. package/app/composables/useCopyToClipboard.spec.ts +27 -0
  32. package/app/composables/useCopyToClipboard.ts +17 -1
  33. package/app/composables/useResultView.ts +10 -21
  34. package/app/stores/github.ts +24 -0
  35. package/app/stores/pipelines.ts +9 -0
  36. package/app/utils/pipeline.ts +25 -1
  37. package/i18n/locales/de.json +20 -0
  38. package/i18n/locales/en.json +20 -0
  39. package/i18n/locales/es.json +20 -0
  40. package/i18n/locales/fr.json +20 -0
  41. package/i18n/locales/he.json +20 -0
  42. package/i18n/locales/it.json +20 -0
  43. package/i18n/locales/ja.json +20 -0
  44. package/i18n/locales/pl.json +20 -0
  45. package/i18n/locales/tr.json +20 -0
  46. package/i18n/locales/uk.json +20 -0
  47. package/package.json +2 -2
@@ -12,7 +12,7 @@ import type {
12
12
  RequirementPriority,
13
13
  SpecModule,
14
14
  } from '~/types/spec'
15
- import IconButton from '~/components/common/IconButton.vue'
15
+ import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
16
16
 
17
17
  const { t } = useI18n()
18
18
  const board = useBoardStore()
@@ -121,291 +121,260 @@ function kindLabel(item: RequirementItem): string {
121
121
  </script>
122
122
 
123
123
  <template>
124
- <Teleport to="body">
124
+ <ResultWindowShell
125
+ :open="open"
126
+ icon="i-lucide-scroll-text"
127
+ icon-class="bg-indigo-500/15 text-indigo-300"
128
+ :title="t('spec.title')"
129
+ :subtitle="block ? spec?.service || block.title : undefined"
130
+ variant="centered"
131
+ width="5xl"
132
+ @close="close"
133
+ >
134
+ <!-- view toggle: Gherkin only when the spec (and its feature files) are on main -->
135
+ <template v-if="present" #header-extras>
136
+ <div class="flex items-center rounded-lg border border-slate-700 p-0.5">
137
+ <UButton
138
+ :color="mode === 'structured' ? 'primary' : 'neutral'"
139
+ :variant="mode === 'structured' ? 'soft' : 'ghost'"
140
+ size="xs"
141
+ icon="i-lucide-list-tree"
142
+ @click="
143
+ () => {
144
+ mode = 'structured'
145
+ }
146
+ "
147
+ >
148
+ {{ t('spec.mode.structured') }}
149
+ </UButton>
150
+ <UButton
151
+ :color="mode === 'gherkin' ? 'primary' : 'neutral'"
152
+ :variant="mode === 'gherkin' ? 'soft' : 'ghost'"
153
+ size="xs"
154
+ icon="i-lucide-square-check-big"
155
+ :disabled="!hasGherkin"
156
+ :title="hasGherkin ? t('spec.mode.gherkinTooltip') : t('spec.mode.gherkinNone')"
157
+ @click="
158
+ () => {
159
+ mode = 'gherkin'
160
+ }
161
+ "
162
+ >
163
+ {{ t('spec.mode.gherkin') }}
164
+ </UButton>
165
+ </div>
166
+ </template>
167
+
168
+ <!-- loading -->
125
169
  <div
126
- v-if="open"
127
- class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
128
- @click.self="close"
170
+ v-if="loading && !view"
171
+ class="flex flex-1 items-center justify-center gap-2 text-sm text-slate-400"
129
172
  >
130
- <div
131
- class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
132
- role="dialog"
133
- aria-modal="true"
173
+ <UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
174
+ {{ t('spec.loading') }}
175
+ </div>
176
+
177
+ <!-- error -->
178
+ <div
179
+ v-else-if="errored"
180
+ class="flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center text-sm text-slate-400"
181
+ >
182
+ <UIcon name="i-lucide-triangle-alert" class="h-6 w-6 text-amber-400" />
183
+ {{ t('spec.error') }}
184
+ <UButton
185
+ icon="i-lucide-rotate-cw"
186
+ color="neutral"
187
+ variant="soft"
188
+ size="xs"
189
+ :loading="loading"
190
+ @click="retry"
134
191
  >
135
- <!-- header -->
136
- <header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
137
- <div
138
- class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-indigo-500/15"
139
- >
140
- <UIcon name="i-lucide-scroll-text" class="h-5 w-5 text-indigo-300" />
141
- </div>
142
- <div class="min-w-0">
143
- <h1 class="truncate text-base font-semibold text-white">{{ t('spec.title') }}</h1>
144
- <p v-if="block" class="truncate text-xs text-slate-500">
145
- {{ spec?.service || block.title }}
146
- </p>
192
+ {{ t('common.retry') }}
193
+ </UButton>
194
+ </div>
195
+
196
+ <!-- empty: no spec on the repo's default branch yet -->
197
+ <div
198
+ v-else-if="!present"
199
+ class="flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center"
200
+ >
201
+ <UIcon name="i-lucide-scroll-text" class="h-8 w-8 text-slate-600" />
202
+ <div>
203
+ <p class="text-sm font-medium text-slate-300">{{ t('spec.empty.title') }}</p>
204
+ <p class="mx-auto mt-1 max-w-md text-xs text-slate-500">
205
+ {{ t('spec.empty.description') }}
206
+ </p>
207
+ </div>
208
+ </div>
209
+
210
+ <!-- spec body: navigable tree + detail -->
211
+ <div v-else class="flex min-h-0 flex-1">
212
+ <!-- nav: modules → feature groups -->
213
+ <nav class="w-64 shrink-0 overflow-y-auto border-e border-slate-800 px-3 py-4">
214
+ <UButton
215
+ block
216
+ class="mb-2 justify-start"
217
+ :color="selected === null ? 'primary' : 'neutral'"
218
+ :variant="selected === null ? 'soft' : 'ghost'"
219
+ size="xs"
220
+ icon="i-lucide-info"
221
+ @click="
222
+ () => {
223
+ selected = null
224
+ }
225
+ "
226
+ >
227
+ {{ t('spec.overview') }}
228
+ </UButton>
229
+ <div v-for="(mod, mi) in modules" :key="mi" class="mb-3">
230
+ <div class="px-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
231
+ {{ mod.name }}
147
232
  </div>
148
- <div class="ms-auto flex items-center gap-1.5">
149
- <!-- view toggle: Gherkin only when the spec (and its feature files) are on main -->
150
- <div v-if="present" class="flex items-center rounded-lg border border-slate-700 p-0.5">
151
- <UButton
152
- :color="mode === 'structured' ? 'primary' : 'neutral'"
153
- :variant="mode === 'structured' ? 'soft' : 'ghost'"
154
- size="xs"
155
- icon="i-lucide-list-tree"
156
- @click="
157
- () => {
158
- mode = 'structured'
159
- }
233
+ <ul class="space-y-0.5">
234
+ <li v-for="(group, gi) in mod.groups ?? []" :key="gi">
235
+ <button
236
+ type="button"
237
+ class="flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-start text-[13px] transition"
238
+ :class="
239
+ selected?.m === mi && selected?.g === gi
240
+ ? 'bg-indigo-500/15 text-indigo-200'
241
+ : 'text-slate-300 hover:bg-slate-800'
160
242
  "
243
+ @click="selectGroup(mi, gi)"
161
244
  >
162
- {{ t('spec.mode.structured') }}
163
- </UButton>
164
- <UButton
165
- :color="mode === 'gherkin' ? 'primary' : 'neutral'"
166
- :variant="mode === 'gherkin' ? 'soft' : 'ghost'"
167
- size="xs"
168
- icon="i-lucide-square-check-big"
169
- :disabled="!hasGherkin"
170
- :title="hasGherkin ? t('spec.mode.gherkinTooltip') : t('spec.mode.gherkinNone')"
171
- @click="
172
- () => {
173
- mode = 'gherkin'
174
- }
175
- "
176
- >
177
- {{ t('spec.mode.gherkin') }}
178
- </UButton>
179
- </div>
180
- <IconButton
181
- icon="i-lucide-x"
182
- color="neutral"
183
- variant="ghost"
184
- size="sm"
185
- :label="t('common.close')"
186
- @click="close"
187
- />
188
- </div>
189
- </header>
190
-
191
- <!-- loading -->
192
- <div
193
- v-if="loading && !view"
194
- class="flex flex-1 items-center justify-center gap-2 text-sm text-slate-400"
195
- >
196
- <UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
197
- {{ t('spec.loading') }}
245
+ <span class="truncate">{{ group.name }}</span>
246
+ <span class="shrink-0 text-[10px] text-slate-500">{{ reqCount(group) }}</span>
247
+ </button>
248
+ </li>
249
+ <li
250
+ v-if="(mod.groups?.length ?? 0) === 0"
251
+ class="px-2 py-1 text-[11px] italic text-slate-600"
252
+ >
253
+ {{ t('spec.noFeatureGroups') }}
254
+ </li>
255
+ </ul>
198
256
  </div>
257
+ </nav>
199
258
 
200
- <!-- error -->
201
- <div
202
- v-else-if="errored"
203
- class="flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center text-sm text-slate-400"
204
- >
205
- <UIcon name="i-lucide-triangle-alert" class="h-6 w-6 text-amber-400" />
206
- {{ t('spec.error') }}
207
- <UButton
208
- icon="i-lucide-rotate-cw"
209
- color="neutral"
210
- variant="soft"
211
- size="xs"
212
- :loading="loading"
213
- @click="retry"
214
- >
215
- {{ t('common.retry') }}
216
- </UButton>
217
- </div>
259
+ <!-- detail -->
260
+ <div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
261
+ <!-- service overview -->
262
+ <template v-if="selected === null">
263
+ <h2 class="text-lg font-semibold text-white">{{ spec?.service }}</h2>
264
+ <p v-if="spec?.summary" class="mt-2 whitespace-pre-line text-sm text-slate-300">
265
+ {{ spec.summary }}
266
+ </p>
267
+ <p v-else class="mt-2 text-sm text-slate-500">{{ t('spec.noSummary') }}</p>
268
+ <p class="mt-4 text-xs text-slate-500">
269
+ {{
270
+ hasGherkin
271
+ ? t('spec.moduleHintGherkin', { count: modules.length }, modules.length)
272
+ : t('spec.moduleHint', { count: modules.length }, modules.length)
273
+ }}
274
+ </p>
275
+ </template>
218
276
 
219
- <!-- empty: no spec on the repo's default branch yet -->
220
- <div
221
- v-else-if="!present"
222
- class="flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center"
223
- >
224
- <UIcon name="i-lucide-scroll-text" class="h-8 w-8 text-slate-600" />
225
- <div>
226
- <p class="text-sm font-medium text-slate-300">{{ t('spec.empty.title') }}</p>
227
- <p class="mx-auto mt-1 max-w-md text-xs text-slate-500">
228
- {{ t('spec.empty.description') }}
229
- </p>
277
+ <!-- selected feature group -->
278
+ <template v-else-if="selectedGroup">
279
+ <div class="mb-1 text-[11px] uppercase tracking-wide text-slate-500">
280
+ {{ selectedModule?.name }}
230
281
  </div>
231
- </div>
282
+ <h2 class="text-lg font-semibold text-white">{{ selectedGroup.name }}</h2>
283
+ <p v-if="selectedGroup.summary" class="mt-1 text-sm text-slate-400">
284
+ {{ selectedGroup.summary }}
285
+ </p>
232
286
 
233
- <!-- spec body: navigable tree + detail -->
234
- <div v-else class="flex min-h-0 flex-1">
235
- <!-- nav: modules → feature groups -->
236
- <nav class="w-64 shrink-0 overflow-y-auto border-e border-slate-800 px-3 py-4">
237
- <UButton
238
- block
239
- class="mb-2 justify-start"
240
- :color="selected === null ? 'primary' : 'neutral'"
241
- :variant="selected === null ? 'soft' : 'ghost'"
242
- size="xs"
243
- icon="i-lucide-info"
244
- @click="
245
- () => {
246
- selected = null
247
- }
248
- "
287
+ <!-- GHERKIN view: the rendered .feature file for this group -->
288
+ <template v-if="mode === 'gherkin'">
289
+ <pre
290
+ v-if="selectedFeature"
291
+ class="mt-4 overflow-x-auto rounded-lg border border-slate-800 bg-slate-950/60 p-4 text-[12.5px] leading-relaxed text-slate-200"
292
+ ><code>{{ selectedFeature.content }}</code></pre>
293
+ <div
294
+ v-else
295
+ class="mt-4 rounded-lg border border-dashed border-slate-700 p-6 text-center text-sm text-slate-500"
249
296
  >
250
- {{ t('spec.overview') }}
251
- </UButton>
252
- <div v-for="(mod, mi) in modules" :key="mi" class="mb-3">
297
+ {{ t('spec.noGherkinForGroup') }}
298
+ </div>
299
+ </template>
300
+
301
+ <!-- STRUCTURED view: requirements + acceptance + domain rules -->
302
+ <template v-else>
303
+ <div v-if="reqCount(selectedGroup) === 0" class="mt-4 text-sm text-slate-500">
304
+ {{ t('spec.noRequirements') }}
305
+ </div>
306
+ <ul class="mt-4 space-y-4">
307
+ <li
308
+ v-for="req in selectedGroup.requirements ?? []"
309
+ :key="req.id"
310
+ class="rounded-lg border border-slate-800 bg-slate-900/60 p-4"
311
+ >
312
+ <div class="flex items-start justify-between gap-3">
313
+ <h3 class="text-sm font-semibold text-slate-100">{{ req.title }}</h3>
314
+ <div class="flex shrink-0 items-center gap-1.5">
315
+ <UBadge :color="priorityMeta(req).chip as any" variant="subtle" size="sm">
316
+ {{ priorityMeta(req).label }}
317
+ </UBadge>
318
+ <UBadge color="neutral" variant="subtle" size="sm">{{ kindLabel(req) }}</UBadge>
319
+ </div>
320
+ </div>
321
+ <p class="mt-1.5 whitespace-pre-line text-[13px] leading-relaxed text-slate-300">
322
+ {{ req.statement }}
323
+ </p>
324
+ <!-- acceptance criteria (Given/When/Then) -->
325
+ <div v-if="(req.acceptance?.length ?? 0) > 0" class="mt-3 space-y-1.5">
326
+ <div
327
+ v-for="ac in req.acceptance ?? []"
328
+ :key="ac.id"
329
+ class="rounded-md border border-slate-800 bg-slate-950/50 px-3 py-2 text-[12.5px] leading-relaxed"
330
+ >
331
+ <p class="text-slate-300">
332
+ <span class="font-semibold text-emerald-400">{{
333
+ t('spec.acceptance.given')
334
+ }}</span>
335
+ {{ ac.given }}
336
+ </p>
337
+ <p class="text-slate-300">
338
+ <span class="font-semibold text-sky-400">{{
339
+ t('spec.acceptance.when')
340
+ }}</span>
341
+ {{ ac.when }}
342
+ </p>
343
+ <p class="text-slate-300">
344
+ <span class="font-semibold text-violet-400">{{
345
+ t('spec.acceptance.then')
346
+ }}</span>
347
+ {{ ac.outcome }}
348
+ </p>
349
+ </div>
350
+ </div>
351
+ </li>
352
+ </ul>
353
+
354
+ <!-- domain rules / invariants scoped to this group -->
355
+ <div v-if="(selectedGroup.rules?.length ?? 0) > 0" class="mt-6">
253
356
  <div
254
- class="px-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500"
357
+ class="mb-2 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-400"
255
358
  >
256
- {{ mod.name }}
359
+ <UIcon name="i-lucide-shield-check" class="h-3.5 w-3.5" />
360
+ {{ t('spec.domainRules') }}
257
361
  </div>
258
- <ul class="space-y-0.5">
259
- <li v-for="(group, gi) in mod.groups ?? []" :key="gi">
260
- <button
261
- type="button"
262
- class="flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-start text-[13px] transition"
263
- :class="
264
- selected?.m === mi && selected?.g === gi
265
- ? 'bg-indigo-500/15 text-indigo-200'
266
- : 'text-slate-300 hover:bg-slate-800'
267
- "
268
- @click="selectGroup(mi, gi)"
269
- >
270
- <span class="truncate">{{ group.name }}</span>
271
- <span class="shrink-0 text-[10px] text-slate-500">{{ reqCount(group) }}</span>
272
- </button>
273
- </li>
362
+ <ul class="space-y-1.5">
274
363
  <li
275
- v-if="(mod.groups?.length ?? 0) === 0"
276
- class="px-2 py-1 text-[11px] italic text-slate-600"
364
+ v-for="rule in selectedGroup.rules ?? []"
365
+ :key="rule.id"
366
+ class="rounded-md border border-slate-800 bg-slate-900/60 px-3 py-2 text-[13px] text-slate-300"
277
367
  >
278
- {{ t('spec.noFeatureGroups') }}
368
+ {{ rule.rule }}
369
+ <span v-if="rule.rationale" class="text-slate-500">{{
370
+ t('spec.ruleRationale', { rationale: rule.rationale })
371
+ }}</span>
279
372
  </li>
280
373
  </ul>
281
374
  </div>
282
- </nav>
283
-
284
- <!-- detail -->
285
- <div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
286
- <!-- service overview -->
287
- <template v-if="selected === null">
288
- <h2 class="text-lg font-semibold text-white">{{ spec?.service }}</h2>
289
- <p v-if="spec?.summary" class="mt-2 whitespace-pre-line text-sm text-slate-300">
290
- {{ spec.summary }}
291
- </p>
292
- <p v-else class="mt-2 text-sm text-slate-500">{{ t('spec.noSummary') }}</p>
293
- <p class="mt-4 text-xs text-slate-500">
294
- {{
295
- hasGherkin
296
- ? t('spec.moduleHintGherkin', { count: modules.length }, modules.length)
297
- : t('spec.moduleHint', { count: modules.length }, modules.length)
298
- }}
299
- </p>
300
- </template>
301
-
302
- <!-- selected feature group -->
303
- <template v-else-if="selectedGroup">
304
- <div class="mb-1 text-[11px] uppercase tracking-wide text-slate-500">
305
- {{ selectedModule?.name }}
306
- </div>
307
- <h2 class="text-lg font-semibold text-white">{{ selectedGroup.name }}</h2>
308
- <p v-if="selectedGroup.summary" class="mt-1 text-sm text-slate-400">
309
- {{ selectedGroup.summary }}
310
- </p>
311
-
312
- <!-- GHERKIN view: the rendered .feature file for this group -->
313
- <template v-if="mode === 'gherkin'">
314
- <pre
315
- v-if="selectedFeature"
316
- class="mt-4 overflow-x-auto rounded-lg border border-slate-800 bg-slate-950/60 p-4 text-[12.5px] leading-relaxed text-slate-200"
317
- ><code>{{ selectedFeature.content }}</code></pre>
318
- <div
319
- v-else
320
- class="mt-4 rounded-lg border border-dashed border-slate-700 p-6 text-center text-sm text-slate-500"
321
- >
322
- {{ t('spec.noGherkinForGroup') }}
323
- </div>
324
- </template>
325
-
326
- <!-- STRUCTURED view: requirements + acceptance + domain rules -->
327
- <template v-else>
328
- <div v-if="reqCount(selectedGroup) === 0" class="mt-4 text-sm text-slate-500">
329
- {{ t('spec.noRequirements') }}
330
- </div>
331
- <ul class="mt-4 space-y-4">
332
- <li
333
- v-for="req in selectedGroup.requirements ?? []"
334
- :key="req.id"
335
- class="rounded-lg border border-slate-800 bg-slate-900/60 p-4"
336
- >
337
- <div class="flex items-start justify-between gap-3">
338
- <h3 class="text-sm font-semibold text-slate-100">{{ req.title }}</h3>
339
- <div class="flex shrink-0 items-center gap-1.5">
340
- <UBadge :color="priorityMeta(req).chip as any" variant="subtle" size="sm">
341
- {{ priorityMeta(req).label }}
342
- </UBadge>
343
- <UBadge color="neutral" variant="subtle" size="sm">{{
344
- kindLabel(req)
345
- }}</UBadge>
346
- </div>
347
- </div>
348
- <p
349
- class="mt-1.5 whitespace-pre-line text-[13px] leading-relaxed text-slate-300"
350
- >
351
- {{ req.statement }}
352
- </p>
353
- <!-- acceptance criteria (Given/When/Then) -->
354
- <div v-if="(req.acceptance?.length ?? 0) > 0" class="mt-3 space-y-1.5">
355
- <div
356
- v-for="ac in req.acceptance ?? []"
357
- :key="ac.id"
358
- class="rounded-md border border-slate-800 bg-slate-950/50 px-3 py-2 text-[12.5px] leading-relaxed"
359
- >
360
- <p class="text-slate-300">
361
- <span class="font-semibold text-emerald-400">{{
362
- t('spec.acceptance.given')
363
- }}</span>
364
- {{ ac.given }}
365
- </p>
366
- <p class="text-slate-300">
367
- <span class="font-semibold text-sky-400">{{
368
- t('spec.acceptance.when')
369
- }}</span>
370
- {{ ac.when }}
371
- </p>
372
- <p class="text-slate-300">
373
- <span class="font-semibold text-violet-400">{{
374
- t('spec.acceptance.then')
375
- }}</span>
376
- {{ ac.outcome }}
377
- </p>
378
- </div>
379
- </div>
380
- </li>
381
- </ul>
382
-
383
- <!-- domain rules / invariants scoped to this group -->
384
- <div v-if="(selectedGroup.rules?.length ?? 0) > 0" class="mt-6">
385
- <div
386
- class="mb-2 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-400"
387
- >
388
- <UIcon name="i-lucide-shield-check" class="h-3.5 w-3.5" />
389
- {{ t('spec.domainRules') }}
390
- </div>
391
- <ul class="space-y-1.5">
392
- <li
393
- v-for="rule in selectedGroup.rules ?? []"
394
- :key="rule.id"
395
- class="rounded-md border border-slate-800 bg-slate-900/60 px-3 py-2 text-[13px] text-slate-300"
396
- >
397
- {{ rule.rule }}
398
- <span v-if="rule.rationale" class="text-slate-500">{{
399
- t('spec.ruleRationale', { rationale: rule.rationale })
400
- }}</span>
401
- </li>
402
- </ul>
403
- </div>
404
- </template>
405
- </template>
406
- </div>
407
- </div>
375
+ </template>
376
+ </template>
408
377
  </div>
409
378
  </div>
410
- </Teleport>
379
+ </ResultWindowShell>
411
380
  </template>
@@ -30,12 +30,10 @@ const blobs = useArtifactBlobs()
30
30
  onUnmounted(() => blobs.revokeAll())
31
31
 
32
32
  // Shared seam contract (open/blockId/close). No `onOpen` loader: this window reads its report
33
- // straight off the execution step, so there's nothing to fetch on open. `manageEscape: false`
34
- // — `ResultWindowShell` owns Escape (and the focus trap + scroll lock + stacking) via the
35
- // shared overlay behaviour; the nested lightbox layers above it on the same stack.
36
- const { open, blockId, instanceId, stepIndex, close } = useResultView('tester', {
37
- manageEscape: false,
38
- })
33
+ // straight off the execution step, so there's nothing to fetch on open. `ResultWindowShell`
34
+ // owns Escape (and the focus trap + scroll lock + stacking) via the shared overlay behaviour;
35
+ // the nested lightbox layers above it on the same stack.
36
+ const { open, blockId, instanceId, stepIndex, close } = useResultView('tester')
39
37
  const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
40
38
  const headerTitle = computed(
41
39
  () => `${t('testing.title')}${block.value ? ` — ${block.value.title}` : ''}`,
@@ -26,11 +26,9 @@ const access = useWorkspaceAccess()
26
26
  const blobs = useArtifactBlobs()
27
27
  onUnmounted(() => blobs.revokeAll())
28
28
 
29
- // `manageEscape: false` — `ResultWindowShell` owns Escape (and the focus trap + scroll lock +
30
- // stacking); the nested lightbox layers above it on the same shared overlay stack.
31
- const { open, blockId, instanceId, stepIndex, close } = useResultView('visual-confirm', {
32
- manageEscape: false,
33
- })
29
+ // `ResultWindowShell` owns Escape (and the focus trap + scroll lock + stacking); the nested
30
+ // lightbox layers above it on the same shared overlay stack.
31
+ const { open, blockId, instanceId, stepIndex, close } = useResultView('visual-confirm')
34
32
  const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
35
33
  const headerTitle = computed(() =>
36
34
  block.value
@@ -13,6 +13,7 @@ import {
13
13
  listGitHubIssuesContract,
14
14
  listGitHubPullsContract,
15
15
  listGitHubReposContract,
16
+ listGitHubRepoFilesContract,
16
17
  listGitHubRepoTreeContract,
17
18
  mergeGitHubPullRequestContract,
18
19
  openGitHubPullRequestContract,
@@ -92,6 +93,13 @@ export function githubApi({ send, ws }: ApiContext) {
92
93
  queryParams: { path },
93
94
  }),
94
95
 
96
+ // List every file in a repo (whole tree, one recursive read) for file-path search.
97
+ listGitHubRepoFiles: (workspaceId: string, repoGithubId: number) =>
98
+ send(listGitHubRepoFilesContract, {
99
+ pathPrefix: ws(workspaceId),
100
+ pathParams: { repoGithubId: String(repoGithubId) },
101
+ }),
102
+
95
103
  listGitHubBranches: (workspaceId: string, repoGithubId: number) =>
96
104
  send(listGitHubBranchesContract, {
97
105
  pathPrefix: ws(workspaceId),