@cat-factory/app 0.254.2 → 0.255.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.
- package/README.md +27 -5
- package/app/components/palettes/AgentPalette.vue +3 -2
- package/app/components/palettes/PipelinePurposeSelect.vue +9 -13
- package/app/components/pipeline/PipelineBuilder.vue +55 -8
- package/app/composables/usePipelineHealth.spec.ts +1 -0
- package/app/stores/pipelines/context.ts +1 -1
- package/app/stores/pipelines/draftActions.ts +2 -2
- package/app/stores/pipelines/persistence.ts +4 -4
- package/app/stores/pipelines.ts +8 -4
- package/app/utils/agentPalette.spec.ts +17 -11
- package/app/utils/agentPalette.ts +1 -1
- package/app/utils/pipeline.spec.ts +24 -20
- package/app/utils/pipelineLibrary.spec.ts +101 -0
- package/app/utils/pipelineLibrary.ts +69 -0
- package/i18n/locales/de.json +4 -1
- package/i18n/locales/en.json +7 -1
- package/i18n/locales/es.json +4 -1
- package/i18n/locales/fr.json +4 -1
- package/i18n/locales/he.json +4 -1
- package/i18n/locales/it.json +4 -1
- package/i18n/locales/ja.json +4 -1
- package/i18n/locales/pl.json +4 -1
- package/i18n/locales/tr.json +4 -1
- package/i18n/locales/uk.json +4 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -271,12 +271,20 @@ The builder's palette narrows on two axes, and both controls sit on one row abov
|
|
|
271
271
|
(`PipelinePurposeSelect` above `AgentTierSelect`), each with its own "n hidden" hint so neither
|
|
272
272
|
narrowing reads as an empty catalog. The tier says how deep to look; the **purpose** says what
|
|
273
273
|
the pipeline is for (`build` / `document` / `review` / `research` / `planning`), and the palette
|
|
274
|
-
drops the categories that purpose has no use for.
|
|
274
|
+
drops the categories that purpose has no use for. It reaches past the palette: the saved-pipeline
|
|
275
|
+
library in the builder's third column lists the pipelines built for the purpose being edited, so
|
|
276
|
+
one dial narrows both ends of the slideover. The purpose is not a view preference either: it is
|
|
275
277
|
saved on the pipeline and also decides which task pickers offer it, which is why the control
|
|
276
278
|
writes through to the draft while the tier writes to its own store.
|
|
277
279
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
+
`Pipeline.purpose` is MANDATORY, so there is no unclassified state for any of these surfaces to
|
|
281
|
+
invent a policy for: a new draft starts at `build` (what an unclassified pipeline always behaved
|
|
282
|
+
as) and the dial only moves it. What each surface still has to read carefully is a purpose the
|
|
283
|
+
BUNDLE cannot name, which the persisted, closed vocabulary makes reachable in both directions (a
|
|
284
|
+
browser older than a new member, a row older than a retired one).
|
|
285
|
+
|
|
286
|
+
Purpose is filtered by three predicates in `@cat-factory/contracts`, and the difference between
|
|
287
|
+
the first two is the point:
|
|
280
288
|
|
|
281
289
|
- `purposeSuggestsAgentCategory` is **relevance**: what the palette OFFERS. Opinionated (a
|
|
282
290
|
review pipeline designs nothing; a planning pipeline has no pull request to gate), because a
|
|
@@ -284,14 +292,28 @@ them is the point:
|
|
|
284
292
|
- `purposeAllowsAgentCategory` is **compatibility**: what the builder will SAVE. It states only
|
|
285
293
|
what is contradictory (a pipeline that writes no code carrying an implementation step) and
|
|
286
294
|
drives the draft's conflict warning.
|
|
295
|
+
- `pipelineMatchesPurpose` is **membership**: which SAVED pipelines the builder's library lists,
|
|
296
|
+
reduced with the label and archive dials in `utils/pipelineLibrary.ts`. The one dial in that
|
|
297
|
+
column whose control is elsewhere, so it is the one that owes a "n hidden" hint. It may be
|
|
298
|
+
exact where the pickers' `pipelineAllowedForTaskType` is permissive, because it narrows a list
|
|
299
|
+
somebody is BROWSING rather than one they are about to run from: two known purposes never mix,
|
|
300
|
+
while a pipeline whose classifier this build cannot NAME is listed at every purpose rather than
|
|
301
|
+
vanishing from the editor that has to fix it.
|
|
302
|
+
|
|
303
|
+
The library's purpose is a BROWSING dial of its own, defaulting to the draft's and relaxed by the
|
|
304
|
+
hint itself ("show every purpose"). Reading the draft directly is the trap: it is an authoring
|
|
305
|
+
field with no "off" setting, so a hint that only NAMES the absence would send the reader to a
|
|
306
|
+
control whose every setting narrows and whose every change is saved. A dial that hides rows owes
|
|
307
|
+
both a count and a way back, and the way back may not be an edit.
|
|
287
308
|
|
|
288
309
|
Relevance is a subset of compatibility, asserted over the whole grid in `pipeline.spec.ts`. Keep
|
|
289
310
|
it that way: the palette may hide what the save gate tolerates, so tightening the relevance table
|
|
290
311
|
never turns a stored pipeline into one its own editor refuses, but offering a kind the save gate
|
|
291
312
|
then rejects would be a dead end with the refusal arriving after the work.
|
|
292
313
|
|
|
293
|
-
**Each hint counts what relaxing THAT dial alone would reveal**, which is why
|
|
294
|
-
function (`utils/agentPalette.ts`
|
|
314
|
+
**Each hint counts what relaxing THAT dial alone would reveal**, which is why each reduction is one
|
|
315
|
+
function (`utils/agentPalette.ts` for the catalog, `utils/pipelineLibrary.ts` for the library)
|
|
316
|
+
rather than chained filters at the call site. Chaining them
|
|
295
317
|
and subtracting the lengths gives the second dial an honest count and hands the first one the whole
|
|
296
318
|
rest of the catalog: at the default `basic` tier a `planning` pipeline claimed thirteen kinds hidden
|
|
297
319
|
for its purpose when switching back to Build revealed three, the other ten being tier-hidden either
|
|
@@ -17,8 +17,9 @@ defineEmits<{
|
|
|
17
17
|
}>()
|
|
18
18
|
// The purpose of the pipeline being built, edited right here by the control above the catalog.
|
|
19
19
|
// It narrows the palette to the categories that purpose has any use for (a review pipeline
|
|
20
|
-
// designs nothing and builds nothing, see `purposeSuggestsAgentCategory`)
|
|
21
|
-
|
|
20
|
+
// designs nothing and builds nothing, see `purposeSuggestsAgentCategory`). Required, like the
|
|
21
|
+
// field it edits: a draft is classified from the moment it exists.
|
|
22
|
+
const props = defineProps<{ purpose: PipelinePurpose }>()
|
|
22
23
|
|
|
23
24
|
// The post-release-health gate is only meaningful — and only accepted by the backend —
|
|
24
25
|
// with an observability integration connected, so it appears in the palette ONLY then.
|
|
@@ -13,8 +13,8 @@ import { isPipelinePurpose, PIPELINE_PURPOSES, type PipelinePurpose } from '@cat
|
|
|
13
13
|
const { t } = useI18n()
|
|
14
14
|
|
|
15
15
|
const props = defineProps<{
|
|
16
|
-
/** The draft's purpose.
|
|
17
|
-
purpose
|
|
16
|
+
/** The draft's purpose. Required: every pipeline carries one, the builder's default being `build`. */
|
|
17
|
+
purpose: PipelinePurpose
|
|
18
18
|
/** How many agent kinds the current purpose hides. Renders the "n hidden" hint when > 0. */
|
|
19
19
|
hiddenCount?: number
|
|
20
20
|
}>()
|
|
@@ -31,19 +31,15 @@ const PURPOSE_LABELS = computed<Record<PipelinePurpose, string>>(() => ({
|
|
|
31
31
|
planning: t('pipeline.builder.purposeOption.planning'),
|
|
32
32
|
}))
|
|
33
33
|
|
|
34
|
-
// The button text
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
// `purposeSuggestsAgentCategory`), so a blank label would read as a pipeline nobody classified
|
|
42
|
-
// while the saved row says otherwise. The menu still lists every current member, so naming the
|
|
43
|
-
// value is also the way out of it.
|
|
34
|
+
// The button text. A stored purpose this build has no label for is NAMED as unrecognised and
|
|
35
|
+
// quoted back, rather than left to render as an empty string after the colon: it is the one state
|
|
36
|
+
// the user cannot diagnose from the control, because a purpose this bundle cannot name narrows
|
|
37
|
+
// nothing (see `purposeSuggestsAgentCategory`), so a blank label would read as a pipeline nobody
|
|
38
|
+
// classified while the saved row says otherwise. The menu still lists every member this build has,
|
|
39
|
+
// so naming the value is also the way out of it. Reachable even though the field is mandatory: the
|
|
40
|
+
// value is persisted and this bundle can be older than the member it reads.
|
|
44
41
|
const current = computed(() => {
|
|
45
42
|
const purpose = props.purpose
|
|
46
|
-
if (!purpose) return t('pipeline.builder.purposePlaceholder')
|
|
47
43
|
if (!isPipelinePurpose(purpose)) return t('pipeline.builder.purposeUnrecognized', { purpose })
|
|
48
44
|
return PURPOSE_LABELS.value[purpose]
|
|
49
45
|
})
|
|
@@ -11,6 +11,7 @@ import OutputBudgetInput from '~/components/pipeline/OutputBudgetInput.vue'
|
|
|
11
11
|
import BinaryOutputStepPicker from '~/components/pipeline/BinaryOutputStepPicker.vue'
|
|
12
12
|
import { ESTIMATE_AXES, ESTIMATE_AXIS_FIELD, type EstimateAxis } from '~/utils/estimateGating'
|
|
13
13
|
import { showOverrideField } from '~/utils/uiMode'
|
|
14
|
+
import { narrowPipelineLibrary } from '~/utils/pipelineLibrary'
|
|
14
15
|
import {
|
|
15
16
|
agentKindMeta,
|
|
16
17
|
companionForProducer,
|
|
@@ -403,14 +404,26 @@ const showArchived = ref(false)
|
|
|
403
404
|
const allLabels = computed(() =>
|
|
404
405
|
[...new Set(pipelines.pipelines.flatMap((p) => p.labels ?? []))].sort(),
|
|
405
406
|
)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
407
|
+
// The library is narrowed by the same purpose the palette is: browsing at `review` lists the
|
|
408
|
+
// pipelines built for reviewing, not the whole workspace catalog. `narrowPipelineLibrary` owns the
|
|
409
|
+
// reduction so the rule is unit-testable and each hint counts the population its own control can
|
|
410
|
+
// actually reveal, rather than the rows another dial is hiding either way.
|
|
411
|
+
//
|
|
412
|
+
// It is a BROWSING dial of its own, not a read of `draftPurpose`. The draft's purpose is an
|
|
413
|
+
// authoring field with no "unclassified" setting, so keying the library straight off it would
|
|
414
|
+
// leave the only ways back through the hidden rows a re-classification of the pipeline being
|
|
415
|
+
// edited (which persists) or discarding the draft. This ref is what "relax THIS dial alone" means
|
|
416
|
+
// for the purpose: it defaults to following the draft and the hint below toggles it, changing what
|
|
417
|
+
// is listed and nothing that gets saved.
|
|
418
|
+
const browseEveryPurpose = ref(false)
|
|
419
|
+
const library = computed(() =>
|
|
420
|
+
narrowPipelineLibrary(pipelines.pipelines, {
|
|
421
|
+
purpose: browseEveryPurpose.value ? null : pipelines.draftPurpose,
|
|
422
|
+
label: labelFilter.value,
|
|
423
|
+
showArchived: showArchived.value,
|
|
412
424
|
}),
|
|
413
425
|
)
|
|
426
|
+
const visiblePipelines = computed(() => library.value.offered)
|
|
414
427
|
async function toggleArchive(p: Pipeline) {
|
|
415
428
|
try {
|
|
416
429
|
if (p.archived) await pipelines.unarchive(p.id)
|
|
@@ -1131,7 +1144,7 @@ async function clone(p: Pipeline) {
|
|
|
1131
1144
|
{{ t('pipeline.builder.savedPipelines') }}
|
|
1132
1145
|
</h3>
|
|
1133
1146
|
<UButton
|
|
1134
|
-
v-if="
|
|
1147
|
+
v-if="library.archivedInScope"
|
|
1135
1148
|
:icon="showArchived ? 'i-lucide-archive-restore' : 'i-lucide-archive'"
|
|
1136
1149
|
:color="showArchived ? 'primary' : 'neutral'"
|
|
1137
1150
|
variant="ghost"
|
|
@@ -1145,7 +1158,7 @@ async function clone(p: Pipeline) {
|
|
|
1145
1158
|
{{
|
|
1146
1159
|
showArchived
|
|
1147
1160
|
? t('pipeline.builder.hideArchived')
|
|
1148
|
-
: t('pipeline.builder.archivedCount', { count:
|
|
1161
|
+
: t('pipeline.builder.archivedCount', { count: library.archivedInScope })
|
|
1149
1162
|
}}
|
|
1150
1163
|
</UButton>
|
|
1151
1164
|
</div>
|
|
@@ -1174,6 +1187,40 @@ async function clone(p: Pipeline) {
|
|
|
1174
1187
|
</UBadge>
|
|
1175
1188
|
</div>
|
|
1176
1189
|
|
|
1190
|
+
<!-- What the purpose is holding back, and the way past it. Stated here rather than
|
|
1191
|
+
beside the palette's purpose control because this is where the absence is noticed:
|
|
1192
|
+
a narrowed library must never read as the whole one, and a hint that only NAMES an
|
|
1193
|
+
absence sends the reader to the authoring dial one column left, whose every setting
|
|
1194
|
+
narrows and whose every change is saved. -->
|
|
1195
|
+
<div
|
|
1196
|
+
v-if="browseEveryPurpose || library.hiddenByPurpose"
|
|
1197
|
+
class="mb-2 flex shrink-0 flex-wrap items-center gap-x-2 px-1 text-[10px] text-slate-500"
|
|
1198
|
+
>
|
|
1199
|
+
<span>
|
|
1200
|
+
{{
|
|
1201
|
+
browseEveryPurpose
|
|
1202
|
+
? t('pipeline.builder.everyPurposeListed')
|
|
1203
|
+
: t(
|
|
1204
|
+
'pipeline.builder.purposeHiddenPipelines',
|
|
1205
|
+
{ count: library.hiddenByPurpose },
|
|
1206
|
+
library.hiddenByPurpose,
|
|
1207
|
+
)
|
|
1208
|
+
}}
|
|
1209
|
+
</span>
|
|
1210
|
+
<button
|
|
1211
|
+
type="button"
|
|
1212
|
+
class="underline underline-offset-2 hover:text-slate-300"
|
|
1213
|
+
data-testid="pipeline-library-purpose-toggle"
|
|
1214
|
+
@click="browseEveryPurpose = !browseEveryPurpose"
|
|
1215
|
+
>
|
|
1216
|
+
{{
|
|
1217
|
+
browseEveryPurpose
|
|
1218
|
+
? t('pipeline.builder.narrowToDraftPurpose')
|
|
1219
|
+
: t('pipeline.builder.listEveryPurpose')
|
|
1220
|
+
}}
|
|
1221
|
+
</button>
|
|
1222
|
+
</div>
|
|
1223
|
+
|
|
1177
1224
|
<ul class="flex-1 space-y-1.5 pe-1 lg:min-h-0 lg:overflow-y-auto">
|
|
1178
1225
|
<li
|
|
1179
1226
|
v-for="p in visiblePipelines"
|
|
@@ -96,7 +96,7 @@ export interface PipelinesContext {
|
|
|
96
96
|
draftTesterQuality: Ref<(TesterQualityConfig | null)[]>
|
|
97
97
|
draftStepOptions: Ref<(StepOptions | null)[]>
|
|
98
98
|
draftLabels: Ref<string[]>
|
|
99
|
-
draftPurpose: Ref<PipelinePurpose
|
|
99
|
+
draftPurpose: Ref<PipelinePurpose>
|
|
100
100
|
draftName: Ref<string>
|
|
101
101
|
draftDescription: Ref<string>
|
|
102
102
|
editingId: Ref<string | null>
|
|
@@ -165,7 +165,7 @@ export function createPipelineDraftActions(ctx: PipelinesContext) {
|
|
|
165
165
|
draftTesterQuality.value = []
|
|
166
166
|
draftStepOptions.value = []
|
|
167
167
|
draftLabels.value = []
|
|
168
|
-
draftPurpose.value =
|
|
168
|
+
draftPurpose.value = 'build'
|
|
169
169
|
draftName.value = 'New pipeline'
|
|
170
170
|
draftDescription.value = ''
|
|
171
171
|
editingId.value = null
|
|
@@ -185,7 +185,7 @@ export function createPipelineDraftActions(ctx: PipelinesContext) {
|
|
|
185
185
|
)
|
|
186
186
|
draftStepOptions.value = pipeline.agentKinds.map((_, i) => pipeline.stepOptions?.[i] ?? null)
|
|
187
187
|
draftLabels.value = [...(pipeline.labels ?? [])]
|
|
188
|
-
draftPurpose.value = pipeline.purpose
|
|
188
|
+
draftPurpose.value = pipeline.purpose
|
|
189
189
|
draftName.value = pipeline.name
|
|
190
190
|
draftDescription.value = pipeline.description ?? ''
|
|
191
191
|
editingId.value = pipeline.id
|
|
@@ -62,10 +62,10 @@ export function createPipelinePersistence(
|
|
|
62
62
|
stepOptions: ctx.draftStepOptions.value.map((o) => o ?? null),
|
|
63
63
|
// Only send labels when there are any.
|
|
64
64
|
...(ctx.draftLabels.value.length ? { labels: [...ctx.draftLabels.value] } : {}),
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
65
|
+
// ALWAYS sent, like `stepOptions` above and for the same reason: `purpose` is mandatory on
|
|
66
|
+
// create, and on update an omitted field reads as "keep existing", so a re-classification
|
|
67
|
+
// would silently not persist. There is no "unclassified" to clear back to.
|
|
68
|
+
purpose: ctx.draftPurpose.value,
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
package/app/stores/pipelines.ts
CHANGED
|
@@ -70,11 +70,15 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
70
70
|
const draftLabels = ref<string[]>([])
|
|
71
71
|
/**
|
|
72
72
|
* The use-case classifier of the pipeline being assembled/edited (`build` / `document` /
|
|
73
|
-
* `review` / `research` / `planning`)
|
|
74
|
-
*
|
|
75
|
-
*
|
|
73
|
+
* `review` / `research` / `planning`). Drives which task pickers offer the saved pipeline, which
|
|
74
|
+
* agent kinds the builder palette shows (a non-`build` purpose hides the Implementation/Testing
|
|
75
|
+
* kinds) and which saved pipelines the library lists.
|
|
76
|
+
*
|
|
77
|
+
* Never null: `Pipeline.purpose` is mandatory, so a draft is classified from the moment it
|
|
78
|
+
* exists. `build` is the default because it is what an unclassified pipeline has always behaved
|
|
79
|
+
* as, so the dial starts where the old absence pointed rather than at a choice nobody made.
|
|
76
80
|
*/
|
|
77
|
-
const draftPurpose = ref<PipelinePurpose
|
|
81
|
+
const draftPurpose = ref<PipelinePurpose>('build')
|
|
78
82
|
const draftName = ref('New pipeline')
|
|
79
83
|
/** Prose description for the pipeline being assembled/edited (shown in the pickers). */
|
|
80
84
|
const draftDescription = ref('')
|
|
@@ -31,27 +31,33 @@ const CATALOG: AgentArchetype[] = [
|
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
describe('narrowAgentPalette', () => {
|
|
34
|
-
it('narrows nothing for
|
|
35
|
-
const { offered, hiddenByPurpose, hiddenByTier } = narrowAgentPalette(
|
|
34
|
+
it('narrows nothing for a build pipeline at the widest tier', () => {
|
|
35
|
+
const { offered, hiddenByPurpose, hiddenByTier } = narrowAgentPalette(
|
|
36
|
+
CATALOG,
|
|
37
|
+
'build',
|
|
38
|
+
'advanced',
|
|
39
|
+
)
|
|
36
40
|
expect(offered.map((a) => a.kind)).toEqual(CATALOG.map((a) => a.kind))
|
|
37
41
|
expect(hiddenByPurpose).toBe(0)
|
|
38
42
|
expect(hiddenByTier).toBe(0)
|
|
39
43
|
})
|
|
40
44
|
|
|
41
45
|
it('accumulates the tiers when the purpose narrows nothing', () => {
|
|
42
|
-
expect(narrowAgentPalette(CATALOG,
|
|
46
|
+
expect(narrowAgentPalette(CATALOG, 'build', 'basic').offered.map((a) => a.kind)).toEqual([
|
|
43
47
|
'coder',
|
|
44
48
|
'architect',
|
|
45
49
|
'documenter',
|
|
46
50
|
])
|
|
47
|
-
expect(narrowAgentPalette(CATALOG,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
expect(narrowAgentPalette(CATALOG, 'build', 'intermediate').offered.map((a) => a.kind)).toEqual(
|
|
52
|
+
[
|
|
53
|
+
'coder',
|
|
54
|
+
'architect',
|
|
55
|
+
'documenter',
|
|
56
|
+
// An undeclared tier defaults to intermediate, so it appears here.
|
|
57
|
+
'acme-auditor',
|
|
58
|
+
],
|
|
59
|
+
)
|
|
60
|
+
expect(narrowAgentPalette(CATALOG, 'build', 'basic').hiddenByTier).toBe(3)
|
|
55
61
|
})
|
|
56
62
|
|
|
57
63
|
it('counts each dial against what the OTHER already admits', () => {
|
|
@@ -42,7 +42,7 @@ export interface NarrowedAgentPalette<T> {
|
|
|
42
42
|
*/
|
|
43
43
|
export function narrowAgentPalette<T extends Pick<AgentArchetype, 'tier' | 'category'>>(
|
|
44
44
|
archetypes: readonly T[],
|
|
45
|
-
purpose: PipelinePurpose
|
|
45
|
+
purpose: PipelinePurpose,
|
|
46
46
|
tier: AgentTier,
|
|
47
47
|
): NarrowedAgentPalette<T> {
|
|
48
48
|
const relevant = (a: T) => !a.category || purposeSuggestsAgentCategory(purpose, a.category)
|
|
@@ -74,16 +74,19 @@ describe('pipelineAllowedForTaskType', () => {
|
|
|
74
74
|
expect(pipelineAllowedForTaskType(pipeline({ purpose: 'document' }), 'document')).toBe(true)
|
|
75
75
|
expect(pipelineAllowedForTaskType(pipeline({ purpose: 'build' }), 'document')).toBe(false)
|
|
76
76
|
expect(pipelineAllowedForTaskType(pipeline({ purpose: 'research' }), 'document')).toBe(false)
|
|
77
|
-
//
|
|
78
|
-
|
|
77
|
+
// A classifier this build cannot NAME is hidden too: this narrowing requires the explicit
|
|
78
|
+
// member, because a non-document pipeline on a document task authors no document at all.
|
|
79
|
+
expect(pipelineAllowedForTaskType(pipeline({ purpose: UNKNOWN_PURPOSE }), 'document')).toBe(
|
|
80
|
+
false,
|
|
81
|
+
)
|
|
79
82
|
})
|
|
80
83
|
|
|
81
84
|
it('a review task offers ONLY review-purpose pipelines', () => {
|
|
82
85
|
expect(pipelineAllowedForTaskType(pipeline({ purpose: 'review' }), 'review')).toBe(true)
|
|
83
86
|
expect(pipelineAllowedForTaskType(pipeline({ purpose: 'build' }), 'review')).toBe(false)
|
|
84
87
|
expect(pipelineAllowedForTaskType(pipeline({ purpose: 'document' }), 'review')).toBe(false)
|
|
85
|
-
//
|
|
86
|
-
expect(pipelineAllowedForTaskType(pipeline({ purpose:
|
|
88
|
+
// Same disposition as the document task's, for the same reason.
|
|
89
|
+
expect(pipelineAllowedForTaskType(pipeline({ purpose: UNKNOWN_PURPOSE }), 'review')).toBe(false)
|
|
87
90
|
})
|
|
88
91
|
|
|
89
92
|
it('a programmatic task (feature / bug) hides only what cannot ship code', () => {
|
|
@@ -99,26 +102,27 @@ describe('pipelineAllowedForTaskType', () => {
|
|
|
99
102
|
}
|
|
100
103
|
})
|
|
101
104
|
|
|
102
|
-
it('keeps
|
|
103
|
-
// The one place this narrowing runs opposite to the document/review one, and it has to
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// way a document preset is.
|
|
105
|
+
it('keeps a pipeline whose classifier this build cannot name on a feature / bug task', () => {
|
|
106
|
+
// The one place this narrowing runs opposite to the document/review one, and it has to. The
|
|
107
|
+
// value is persisted, so a deployment's own classifier (or one retired since the row was
|
|
108
|
+
// written) reaches a bundle with no member for it — and hiding it would take that pipeline out
|
|
109
|
+
// of the picker people use most, silently, with nothing on screen to explain the absence. It is
|
|
110
|
+
// not known-wrong for a feature the way a document preset is.
|
|
109
111
|
for (const type of ['feature', 'bug'] as const) {
|
|
110
|
-
expect(pipelineAllowedForTaskType(pipeline({ purpose:
|
|
112
|
+
expect(pipelineAllowedForTaskType(pipeline({ purpose: UNKNOWN_PURPOSE }), type)).toBe(true)
|
|
111
113
|
}
|
|
112
|
-
// Still hidden from the types whose narrowing DOES demand the explicit
|
|
113
|
-
expect(pipelineAllowedForTaskType(pipeline({ purpose:
|
|
114
|
-
|
|
114
|
+
// Still hidden from the types whose narrowing DOES demand the explicit member.
|
|
115
|
+
expect(pipelineAllowedForTaskType(pipeline({ purpose: UNKNOWN_PURPOSE }), 'document')).toBe(
|
|
116
|
+
false,
|
|
117
|
+
)
|
|
118
|
+
expect(pipelineAllowedForTaskType(pipeline({ purpose: UNKNOWN_PURPOSE }), 'review')).toBe(false)
|
|
115
119
|
})
|
|
116
120
|
|
|
117
121
|
it('an un-narrowed task type stays unrestricted (spike, ralph, custom, undefined)', () => {
|
|
118
122
|
// A custom (namespaced) deployment type has no purpose mapping we could infer, and `spike` /
|
|
119
123
|
// `ralph` pin their own default pipeline instead of narrowing the picker.
|
|
120
124
|
for (const type of ['spike', 'ralph', 'acme:incident', undefined] as const) {
|
|
121
|
-
for (const purpose of ['build', 'document', 'review', 'research'
|
|
125
|
+
for (const purpose of ['build', 'document', 'review', 'research'] as const) {
|
|
122
126
|
expect(pipelineAllowedForTaskType(pipeline({ purpose }), type)).toBe(true)
|
|
123
127
|
}
|
|
124
128
|
}
|
|
@@ -151,8 +155,8 @@ describe('pipelineAllowedForBlockLevel (initiative binding)', () => {
|
|
|
151
155
|
})
|
|
152
156
|
|
|
153
157
|
describe('purposeAllowsAgentCategory (builder save gate)', () => {
|
|
154
|
-
it('a build
|
|
155
|
-
for (const purpose of ['build',
|
|
158
|
+
it('a build pipeline, and one whose classifier this build cannot name, may use every category', () => {
|
|
159
|
+
for (const purpose of ['build', UNKNOWN_PURPOSE] as const) {
|
|
156
160
|
for (const cat of AGENT_CATEGORIES) {
|
|
157
161
|
expect(purposeAllowsAgentCategory(purpose, cat)).toBe(true)
|
|
158
162
|
}
|
|
@@ -173,8 +177,8 @@ describe('purposeAllowsAgentCategory (builder save gate)', () => {
|
|
|
173
177
|
})
|
|
174
178
|
|
|
175
179
|
describe('purposeSuggestsAgentCategory (builder palette filter)', () => {
|
|
176
|
-
it('offers the whole catalog to a build pipeline and to
|
|
177
|
-
for (const purpose of ['build',
|
|
180
|
+
it('offers the whole catalog to a build pipeline, and to one it cannot name', () => {
|
|
181
|
+
for (const purpose of ['build', UNKNOWN_PURPOSE] as const) {
|
|
178
182
|
for (const cat of AGENT_CATEGORIES) {
|
|
179
183
|
expect(purposeSuggestsAgentCategory(purpose, cat)).toBe(true)
|
|
180
184
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { PIPELINE_PURPOSES } from '@cat-factory/contracts'
|
|
3
|
+
import type { Pipeline } from '~/types/domain'
|
|
4
|
+
import { narrowPipelineLibrary } from '~/utils/pipelineLibrary'
|
|
5
|
+
|
|
6
|
+
type Row = Pick<Pipeline, 'purpose' | 'labels' | 'archived'> & { id: string }
|
|
7
|
+
|
|
8
|
+
const row = (id: string, purpose: Pipeline['purpose'], extra: Partial<Row> = {}): Row => ({
|
|
9
|
+
id,
|
|
10
|
+
purpose,
|
|
11
|
+
...extra,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
// A purpose this build has no member for: what a row saved by a newer build (or before a member
|
|
15
|
+
// was retired) looks like on the way back out of the store. Reachable even though the field is
|
|
16
|
+
// mandatory, which is the whole reason the predicates still narrow the value they are handed.
|
|
17
|
+
const UNKNOWN_PURPOSE = 'acme-migration' as Pipeline['purpose']
|
|
18
|
+
|
|
19
|
+
// Spread across all three dials, so neither count can come out right by coincidence: an archived
|
|
20
|
+
// row and a differently-labelled row are excluded by the OTHER dials either way.
|
|
21
|
+
const LIBRARY: Row[] = [
|
|
22
|
+
row('build-a', 'build'),
|
|
23
|
+
row('build-archived', 'build', { archived: true }),
|
|
24
|
+
row('review-a', 'review'),
|
|
25
|
+
row('review-tagged', 'review', { labels: ['nightly'] }),
|
|
26
|
+
row('review-archived-tagged', 'review', { archived: true, labels: ['nightly'] }),
|
|
27
|
+
row('document-tagged', 'document', { labels: ['nightly'] }),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
describe('narrowPipelineLibrary', () => {
|
|
31
|
+
it('lists every purpose when the library is browsed at none', () => {
|
|
32
|
+
// `null` is the library's own relaxation, not an unclassified draft: `Pipeline.purpose` is
|
|
33
|
+
// mandatory, so the reader asking to browse past the purpose is the only way to get here.
|
|
34
|
+
const { offered, hiddenByPurpose } = narrowPipelineLibrary(LIBRARY, {
|
|
35
|
+
purpose: null,
|
|
36
|
+
showArchived: true,
|
|
37
|
+
})
|
|
38
|
+
expect(offered.map((p) => p.id)).toEqual(LIBRARY.map((p) => p.id))
|
|
39
|
+
expect(hiddenByPurpose).toBe(0)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('lists the pipelines built for the purpose being browsed at', () => {
|
|
43
|
+
const { offered, hiddenByPurpose } = narrowPipelineLibrary(LIBRARY, { purpose: 'review' })
|
|
44
|
+
expect(offered.map((p) => p.id)).toEqual(['review-a', 'review-tagged'])
|
|
45
|
+
// `build-archived` is hidden by the archive toggle either way, so the purpose does not claim it.
|
|
46
|
+
expect(hiddenByPurpose).toBe(2)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('measures the purpose count against what the other dials already admit', () => {
|
|
50
|
+
// The rule `narrowAgentPalette` established: each hint promises "relax THIS dial alone and you
|
|
51
|
+
// get n more". Counting over the whole library would name `build-a` here, which the label
|
|
52
|
+
// filter hides at every purpose.
|
|
53
|
+
const { offered, hiddenByPurpose } = narrowPipelineLibrary(LIBRARY, {
|
|
54
|
+
purpose: 'review',
|
|
55
|
+
label: 'nightly',
|
|
56
|
+
})
|
|
57
|
+
expect(offered.map((p) => p.id)).toEqual(['review-tagged'])
|
|
58
|
+
expect(hiddenByPurpose).toBe(1)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('counts the archived rows the OTHER dials admit, not the whole catalog', () => {
|
|
62
|
+
// The count the "Archived (n)" toggle renders. Against the raw catalog it would promise rows
|
|
63
|
+
// the toggle cannot reveal: two pipelines are archived, but only one of them is a `review` one.
|
|
64
|
+
expect(narrowPipelineLibrary(LIBRARY, { purpose: 'review' }).archivedInScope).toBe(1)
|
|
65
|
+
expect(narrowPipelineLibrary(LIBRARY, { purpose: 'planning' }).archivedInScope).toBe(0)
|
|
66
|
+
expect(narrowPipelineLibrary(LIBRARY, { purpose: null }).archivedInScope).toBe(2)
|
|
67
|
+
expect(
|
|
68
|
+
narrowPipelineLibrary(LIBRARY, { purpose: null, label: 'nightly' }).archivedInScope,
|
|
69
|
+
).toBe(1)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('keeps the archived count steady across the toggle it governs', () => {
|
|
73
|
+
// Unlike `hiddenByPurpose`, this one may NOT drop to zero once the dial is relaxed: it decides
|
|
74
|
+
// whether the toggle is offered at all, so a count that vanished when the toggle worked would
|
|
75
|
+
// strand the archived rows visible with no way to hide them again.
|
|
76
|
+
const shown = narrowPipelineLibrary(LIBRARY, { purpose: 'review', showArchived: true })
|
|
77
|
+
expect(shown.offered.map((p) => p.id)).toEqual([
|
|
78
|
+
'review-a',
|
|
79
|
+
'review-tagged',
|
|
80
|
+
'review-archived-tagged',
|
|
81
|
+
])
|
|
82
|
+
expect(shown.archivedInScope).toBe(1)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('narrows by neither purpose this build cannot name', () => {
|
|
86
|
+
// Same disposition as the palette's: an unrecognised value is one this build has nothing to
|
|
87
|
+
// narrow BY and nothing to narrow it AGAINST, never a reason to guess a current member. A
|
|
88
|
+
// stored row carrying one stays reachable in the editor that has to fix it.
|
|
89
|
+
const stored = [...LIBRARY, row('acme', UNKNOWN_PURPOSE)]
|
|
90
|
+
for (const purpose of PIPELINE_PURPOSES) {
|
|
91
|
+
expect(narrowPipelineLibrary(stored, { purpose }).offered.some((p) => p.id === 'acme')).toBe(
|
|
92
|
+
true,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
expect(narrowPipelineLibrary(stored, { purpose: UNKNOWN_PURPOSE })).toEqual({
|
|
96
|
+
offered: stored.filter((p) => !p.archived),
|
|
97
|
+
hiddenByPurpose: 0,
|
|
98
|
+
archivedInScope: 2,
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { pipelineMatchesPurpose, type PipelinePurpose } from '@cat-factory/contracts'
|
|
2
|
+
import type { Pipeline } from '~/types/domain'
|
|
3
|
+
|
|
4
|
+
/** What the pipeline builder's saved-pipeline library is being browsed at. */
|
|
5
|
+
export interface PipelineLibraryFilters {
|
|
6
|
+
/**
|
|
7
|
+
* The purpose to list, or `null` for "every purpose" — the library's own relaxation, NOT an
|
|
8
|
+
* unclassified draft. `Pipeline.purpose` is mandatory, so the draft always has one; `null` is
|
|
9
|
+
* the reader saying they want to browse past it.
|
|
10
|
+
*/
|
|
11
|
+
purpose?: PipelinePurpose | null
|
|
12
|
+
/** The picked organizational label, or `null` for all of them. */
|
|
13
|
+
label?: string | null
|
|
14
|
+
/** Whether archived pipelines are included. */
|
|
15
|
+
showArchived?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The saved-pipeline library narrowed to what it lists, plus what each hidden-by-default dial is
|
|
20
|
+
* holding back.
|
|
21
|
+
*
|
|
22
|
+
* Both counts are measured against what the OTHER dials already admit, which is the promise
|
|
23
|
+
* `narrowAgentPalette` established: relax THIS dial alone and you get n more. Counting either over
|
|
24
|
+
* the whole catalog would name rows another filter is hiding either way, sending the reader to a
|
|
25
|
+
* control that cannot produce them.
|
|
26
|
+
*
|
|
27
|
+
* The label chips are the one dial with no count, because they are the one dial that already shows
|
|
28
|
+
* its own selection and its own alternatives.
|
|
29
|
+
*/
|
|
30
|
+
export interface NarrowedPipelineLibrary<T> {
|
|
31
|
+
/** The pipelines every dial admits: what the library renders, in input order. */
|
|
32
|
+
offered: T[]
|
|
33
|
+
/** How many more the CURRENT label + archive selection would list at every purpose. */
|
|
34
|
+
hiddenByPurpose: number
|
|
35
|
+
/**
|
|
36
|
+
* How many ARCHIVED pipelines the current purpose + label selection covers.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately independent of `showArchived`, unlike {@link hiddenByPurpose}: this is what the
|
|
39
|
+
* archive toggle governs, and it has to be the same number in both of the toggle's positions or
|
|
40
|
+
* the control that turned archived rows ON would vanish the moment it worked, stranding them
|
|
41
|
+
* visible with no way back. While they are hidden it is also exactly what relaxing that dial
|
|
42
|
+
* alone would add.
|
|
43
|
+
*/
|
|
44
|
+
archivedInScope: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Reduce `pipelines` to the rows the builder's library lists under `filters`.
|
|
49
|
+
*
|
|
50
|
+
* A pure reduction rather than three predicates inlined in the template for the reason
|
|
51
|
+
* {@link import('./agentPalette').narrowAgentPalette} is one: it decides what is VISIBLE and it
|
|
52
|
+
* owes an honest count of what it hid, and a count re-derived at the call site as a subtraction
|
|
53
|
+
* measures the wrong population. The purpose rule itself lives in `@cat-factory/contracts` beside
|
|
54
|
+
* the palette's and the pickers', so the three cannot read a stored `purpose` in different
|
|
55
|
+
* directions.
|
|
56
|
+
*/
|
|
57
|
+
export function narrowPipelineLibrary<T extends Pick<Pipeline, 'purpose' | 'labels' | 'archived'>>(
|
|
58
|
+
pipelines: readonly T[],
|
|
59
|
+
filters: PipelineLibraryFilters,
|
|
60
|
+
): NarrowedPipelineLibrary<T> {
|
|
61
|
+
const labelled = (p: T) => !filters.label || (p.labels ?? []).includes(filters.label)
|
|
62
|
+
const listed = (p: T) => Boolean(filters.showArchived) || !p.archived
|
|
63
|
+
const suits = (p: T) => pipelineMatchesPurpose(p, filters.purpose)
|
|
64
|
+
return {
|
|
65
|
+
offered: pipelines.filter((p) => labelled(p) && listed(p) && suits(p)),
|
|
66
|
+
hiddenByPurpose: pipelines.filter((p) => labelled(p) && listed(p) && !suits(p)).length,
|
|
67
|
+
archivedInScope: pipelines.filter((p) => labelled(p) && suits(p) && p.archived).length,
|
|
68
|
+
}
|
|
69
|
+
}
|
package/i18n/locales/de.json
CHANGED
|
@@ -4213,7 +4213,6 @@
|
|
|
4213
4213
|
"strategy": "Strategie",
|
|
4214
4214
|
"rounds": "Runden",
|
|
4215
4215
|
"purposeLabel": "Zweck",
|
|
4216
|
-
"purposePlaceholder": "Zweck auswählen",
|
|
4217
4216
|
"purposeUnrecognized": "Nicht erkannt ({purpose})",
|
|
4218
4217
|
"purposeOption": {
|
|
4219
4218
|
"build": "Entwicklung",
|
|
@@ -4232,6 +4231,10 @@
|
|
|
4232
4231
|
"removeParticipant": "Teilnehmer entfernen (min. 2)",
|
|
4233
4232
|
"addParticipant": "Teilnehmer hinzufügen",
|
|
4234
4233
|
"savedPipelines": "Gespeicherte Pipelines",
|
|
4234
|
+
"purposeHiddenPipelines": "{count} gespeicherte Pipeline ist für diesen Zweck ausgeblendet. | {count} gespeicherte Pipelines sind für diesen Zweck ausgeblendet.",
|
|
4235
|
+
"everyPurposeListed": "Gespeicherte Pipelines aller Zwecke werden aufgelistet.",
|
|
4236
|
+
"listEveryPurpose": "Alle Zwecke anzeigen",
|
|
4237
|
+
"narrowToDraftPurpose": "Auf diesen Zweck eingrenzen",
|
|
4235
4238
|
"hideArchived": "Archivierte ausblenden",
|
|
4236
4239
|
"archivedCount": "Archiviert ({count})",
|
|
4237
4240
|
"allLabels": "Alle",
|
package/i18n/locales/en.json
CHANGED
|
@@ -4795,7 +4795,6 @@
|
|
|
4795
4795
|
"strategy": "Strategy",
|
|
4796
4796
|
"rounds": "Rounds",
|
|
4797
4797
|
"purposeLabel": "Purpose",
|
|
4798
|
-
"purposePlaceholder": "Select a purpose",
|
|
4799
4798
|
"purposeUnrecognized": "Unrecognized ({purpose})",
|
|
4800
4799
|
"@purposeUnrecognized": {
|
|
4801
4800
|
"description": "Shown on the pipeline-purpose control when the pipeline carries a purpose this build has no label for (a member added by a newer build, or one since retired). The raw stored value is quoted back so it can be recognised and re-picked."
|
|
@@ -4817,6 +4816,13 @@
|
|
|
4817
4816
|
"removeParticipant": "Remove participant (min 2)",
|
|
4818
4817
|
"addParticipant": "Add participant",
|
|
4819
4818
|
"savedPipelines": "Saved pipelines",
|
|
4819
|
+
"purposeHiddenPipelines": "{count} saved pipeline hidden for this purpose. | {count} saved pipelines hidden for this purpose.",
|
|
4820
|
+
"everyPurposeListed": "Listing saved pipelines for every purpose.",
|
|
4821
|
+
"listEveryPurpose": "Show every purpose",
|
|
4822
|
+
"narrowToDraftPurpose": "Narrow to this purpose",
|
|
4823
|
+
"@purposeHiddenPipelines": {
|
|
4824
|
+
"description": "Shown above the saved-pipeline list in the pipeline builder: how many saved pipelines are not listed because they were built for a different purpose than the one being edited."
|
|
4825
|
+
},
|
|
4820
4826
|
"hideArchived": "Hide archived",
|
|
4821
4827
|
"archivedCount": "Archived ({count})",
|
|
4822
4828
|
"allLabels": "All",
|
package/i18n/locales/es.json
CHANGED
|
@@ -4652,7 +4652,6 @@
|
|
|
4652
4652
|
"strategy": "Estrategia",
|
|
4653
4653
|
"rounds": "Rondas",
|
|
4654
4654
|
"purposeLabel": "Propósito",
|
|
4655
|
-
"purposePlaceholder": "Seleccionar un propósito",
|
|
4656
4655
|
"purposeUnrecognized": "No reconocido ({purpose})",
|
|
4657
4656
|
"purposeOption": {
|
|
4658
4657
|
"build": "Desarrollo",
|
|
@@ -4671,6 +4670,10 @@
|
|
|
4671
4670
|
"removeParticipant": "Quitar participante (mín. 2)",
|
|
4672
4671
|
"addParticipant": "Añadir participante",
|
|
4673
4672
|
"savedPipelines": "Pipelines guardados",
|
|
4673
|
+
"purposeHiddenPipelines": "{count} pipeline guardado oculto para este propósito. | {count} pipelines guardados ocultos para este propósito.",
|
|
4674
|
+
"everyPurposeListed": "Se muestran los pipelines guardados de todos los propósitos.",
|
|
4675
|
+
"listEveryPurpose": "Mostrar todos los propósitos",
|
|
4676
|
+
"narrowToDraftPurpose": "Limitar a este propósito",
|
|
4674
4677
|
"hideArchived": "Ocultar archivados",
|
|
4675
4678
|
"archivedCount": "Archivados ({count})",
|
|
4676
4679
|
"allLabels": "Todas",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -4652,7 +4652,6 @@
|
|
|
4652
4652
|
"strategy": "Stratégie",
|
|
4653
4653
|
"rounds": "Tours",
|
|
4654
4654
|
"purposeLabel": "Objectif",
|
|
4655
|
-
"purposePlaceholder": "Sélectionner un objectif",
|
|
4656
4655
|
"purposeUnrecognized": "Non reconnu ({purpose})",
|
|
4657
4656
|
"purposeOption": {
|
|
4658
4657
|
"build": "Développement",
|
|
@@ -4671,6 +4670,10 @@
|
|
|
4671
4670
|
"removeParticipant": "Retirer le participant (min. 2)",
|
|
4672
4671
|
"addParticipant": "Ajouter un participant",
|
|
4673
4672
|
"savedPipelines": "Pipelines enregistrés",
|
|
4673
|
+
"purposeHiddenPipelines": "{count} pipeline enregistré masqué pour cet objectif. | {count} pipelines enregistrés masqués pour cet objectif.",
|
|
4674
|
+
"everyPurposeListed": "Les pipelines enregistrés de tous les objectifs sont affichés.",
|
|
4675
|
+
"listEveryPurpose": "Afficher tous les objectifs",
|
|
4676
|
+
"narrowToDraftPurpose": "Limiter à cet objectif",
|
|
4674
4677
|
"hideArchived": "Masquer les archivés",
|
|
4675
4678
|
"archivedCount": "Archivés ({count})",
|
|
4676
4679
|
"allLabels": "Toutes",
|
package/i18n/locales/he.json
CHANGED
|
@@ -4652,7 +4652,6 @@
|
|
|
4652
4652
|
"strategy": "אסטרטגיה",
|
|
4653
4653
|
"rounds": "סבבים",
|
|
4654
4654
|
"purposeLabel": "מטרה",
|
|
4655
|
-
"purposePlaceholder": "בחר מטרה",
|
|
4656
4655
|
"purposeUnrecognized": "לא מזוהה ({purpose})",
|
|
4657
4656
|
"purposeOption": {
|
|
4658
4657
|
"build": "פיתוח",
|
|
@@ -4671,6 +4670,10 @@
|
|
|
4671
4670
|
"removeParticipant": "הסר משתתף (מינימום 2)",
|
|
4672
4671
|
"addParticipant": "הוסף משתתף",
|
|
4673
4672
|
"savedPipelines": "צינורות שמורים",
|
|
4673
|
+
"purposeHiddenPipelines": "צינור שמור אחד מוסתר עבור מטרה זו. | שני צינורות שמורים מוסתרים עבור מטרה זו. | {count} צינורות שמורים מוסתרים עבור מטרה זו.",
|
|
4674
|
+
"everyPurposeListed": "מוצגים צינורות שמורים מכל המטרות.",
|
|
4675
|
+
"listEveryPurpose": "הצג את כל המטרות",
|
|
4676
|
+
"narrowToDraftPurpose": "צמצם למטרה זו",
|
|
4674
4677
|
"hideArchived": "הסתר מאורכבים",
|
|
4675
4678
|
"archivedCount": "מאורכבים ({count})",
|
|
4676
4679
|
"allLabels": "הכל",
|
package/i18n/locales/it.json
CHANGED
|
@@ -4213,7 +4213,6 @@
|
|
|
4213
4213
|
"strategy": "Strategia",
|
|
4214
4214
|
"rounds": "Giri",
|
|
4215
4215
|
"purposeLabel": "Scopo",
|
|
4216
|
-
"purposePlaceholder": "Seleziona uno scopo",
|
|
4217
4216
|
"purposeUnrecognized": "Non riconosciuto ({purpose})",
|
|
4218
4217
|
"purposeOption": {
|
|
4219
4218
|
"build": "Sviluppo",
|
|
@@ -4232,6 +4231,10 @@
|
|
|
4232
4231
|
"removeParticipant": "Rimuovi partecipante (min 2)",
|
|
4233
4232
|
"addParticipant": "Aggiungi partecipante",
|
|
4234
4233
|
"savedPipelines": "Pipeline salvate",
|
|
4234
|
+
"purposeHiddenPipelines": "{count} pipeline salvata nascosta per questo scopo. | {count} pipeline salvate nascoste per questo scopo.",
|
|
4235
|
+
"everyPurposeListed": "Sono elencate le pipeline salvate di ogni scopo.",
|
|
4236
|
+
"listEveryPurpose": "Mostra tutti gli scopi",
|
|
4237
|
+
"narrowToDraftPurpose": "Limita a questo scopo",
|
|
4235
4238
|
"hideArchived": "Nascondi archiviate",
|
|
4236
4239
|
"archivedCount": "Archiviate ({count})",
|
|
4237
4240
|
"allLabels": "Tutte",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -4652,7 +4652,6 @@
|
|
|
4652
4652
|
"strategy": "戦略",
|
|
4653
4653
|
"rounds": "ラウンド",
|
|
4654
4654
|
"purposeLabel": "目的",
|
|
4655
|
-
"purposePlaceholder": "目的を選択",
|
|
4656
4655
|
"purposeUnrecognized": "認識できません ({purpose})",
|
|
4657
4656
|
"purposeOption": {
|
|
4658
4657
|
"build": "開発",
|
|
@@ -4671,6 +4670,10 @@
|
|
|
4671
4670
|
"removeParticipant": "参加者を削除(最低2)",
|
|
4672
4671
|
"addParticipant": "参加者を追加",
|
|
4673
4672
|
"savedPipelines": "保存済みパイプライン",
|
|
4673
|
+
"purposeHiddenPipelines": "この目的では {count} 件の保存済みパイプラインが非表示です。",
|
|
4674
|
+
"everyPurposeListed": "すべての目的の保存済みパイプラインを表示しています。",
|
|
4675
|
+
"listEveryPurpose": "すべての目的を表示",
|
|
4676
|
+
"narrowToDraftPurpose": "この目的に絞り込む",
|
|
4674
4677
|
"hideArchived": "アーカイブを非表示",
|
|
4675
4678
|
"archivedCount": "アーカイブ済み({count})",
|
|
4676
4679
|
"allLabels": "すべて",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -4652,7 +4652,6 @@
|
|
|
4652
4652
|
"strategy": "Strategia",
|
|
4653
4653
|
"rounds": "Rundy",
|
|
4654
4654
|
"purposeLabel": "Cel",
|
|
4655
|
-
"purposePlaceholder": "Wybierz cel",
|
|
4656
4655
|
"purposeUnrecognized": "Nierozpoznany ({purpose})",
|
|
4657
4656
|
"purposeOption": {
|
|
4658
4657
|
"build": "Programowanie",
|
|
@@ -4671,6 +4670,10 @@
|
|
|
4671
4670
|
"removeParticipant": "Usuń uczestnika (min. 2)",
|
|
4672
4671
|
"addParticipant": "Dodaj uczestnika",
|
|
4673
4672
|
"savedPipelines": "Zapisane pipeline'y",
|
|
4673
|
+
"purposeHiddenPipelines": "{count} zapisany pipeline ukryty dla tego celu. | {count} zapisane pipeline'y ukryte dla tego celu. | {count} zapisanych pipeline'ów ukrytych dla tego celu.",
|
|
4674
|
+
"everyPurposeListed": "Wyświetlane są zapisane pipeline'y wszystkich celów.",
|
|
4675
|
+
"listEveryPurpose": "Pokaż wszystkie cele",
|
|
4676
|
+
"narrowToDraftPurpose": "Zawęź do tego celu",
|
|
4674
4677
|
"hideArchived": "Ukryj zarchiwizowane",
|
|
4675
4678
|
"archivedCount": "Zarchiwizowane ({count})",
|
|
4676
4679
|
"allLabels": "Wszystkie",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -4652,7 +4652,6 @@
|
|
|
4652
4652
|
"strategy": "Strateji",
|
|
4653
4653
|
"rounds": "Tur",
|
|
4654
4654
|
"purposeLabel": "Amaç",
|
|
4655
|
-
"purposePlaceholder": "Bir amaç seçin",
|
|
4656
4655
|
"purposeUnrecognized": "Tanınmayan ({purpose})",
|
|
4657
4656
|
"purposeOption": {
|
|
4658
4657
|
"build": "Geliştirme",
|
|
@@ -4671,6 +4670,10 @@
|
|
|
4671
4670
|
"removeParticipant": "Katılımcıyı kaldır (en az 2)",
|
|
4672
4671
|
"addParticipant": "Katılımcı ekle",
|
|
4673
4672
|
"savedPipelines": "Kayıtlı pipeline'lar",
|
|
4673
|
+
"purposeHiddenPipelines": "Bu amaç için {count} kayıtlı pipeline gizli.",
|
|
4674
|
+
"everyPurposeListed": "Tüm amaçlara ait kayıtlı pipeline'lar listeleniyor.",
|
|
4675
|
+
"listEveryPurpose": "Tüm amaçları göster",
|
|
4676
|
+
"narrowToDraftPurpose": "Bu amaca daralt",
|
|
4674
4677
|
"hideArchived": "Arşivlenenleri gizle",
|
|
4675
4678
|
"archivedCount": "Arşivlenen ({count})",
|
|
4676
4679
|
"allLabels": "Tümü",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -4652,7 +4652,6 @@
|
|
|
4652
4652
|
"strategy": "Стратегія",
|
|
4653
4653
|
"rounds": "Раунди",
|
|
4654
4654
|
"purposeLabel": "Призначення",
|
|
4655
|
-
"purposePlaceholder": "Виберіть призначення",
|
|
4656
4655
|
"purposeUnrecognized": "Нерозпізнано ({purpose})",
|
|
4657
4656
|
"purposeOption": {
|
|
4658
4657
|
"build": "Розробка",
|
|
@@ -4671,6 +4670,10 @@
|
|
|
4671
4670
|
"removeParticipant": "Прибрати учасника (мін. 2)",
|
|
4672
4671
|
"addParticipant": "Додати учасника",
|
|
4673
4672
|
"savedPipelines": "Збережені пайплайни",
|
|
4673
|
+
"purposeHiddenPipelines": "{count} збережений пайплайн приховано для цього призначення. | {count} збережені пайплайни приховано для цього призначення. | {count} збережених пайплайнів приховано для цього призначення.",
|
|
4674
|
+
"everyPurposeListed": "Показано збережені пайплайни всіх призначень.",
|
|
4675
|
+
"listEveryPurpose": "Показати всі призначення",
|
|
4676
|
+
"narrowToDraftPurpose": "Звузити до цього призначення",
|
|
4674
4677
|
"hideArchived": "Сховати архівовані",
|
|
4675
4678
|
"archivedCount": "Архівовані ({count})",
|
|
4676
4679
|
"allLabels": "Усі",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.255.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"valibot": "^1.4.2",
|
|
41
41
|
"vue": "3.5.40",
|
|
42
42
|
"wretch": "^3.0.9",
|
|
43
|
-
"@cat-factory/contracts": "0.
|
|
43
|
+
"@cat-factory/contracts": "0.281.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@toad-contracts/testing": "0.3.2",
|