@cat-factory/app 0.216.0 → 0.217.1

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.
@@ -67,6 +67,12 @@ export type {
67
67
  TaskTypePresentation,
68
68
  TaskTypeFieldDescriptor,
69
69
  TaskTypeFieldOption,
70
+ // The shared descriptor-driven form vocabulary (`contracts/src/form-fields.ts`): one field
71
+ // shape and one filled-value bag behind both the initiative-preset form and a custom task
72
+ // type's per-case form, so `DescriptorFields.vue` renders either.
73
+ DescriptorField,
74
+ DescriptorFieldValue,
75
+ DescriptorFieldValues,
70
76
  Pipeline,
71
77
  PipelinePurpose,
72
78
  SpendStatus,
@@ -0,0 +1,126 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import type { DescriptorField } from '~/types/domain'
3
+ import {
4
+ defaultDescriptorValues,
5
+ descriptorGroupValue,
6
+ setDescriptorCheckbox,
7
+ setDescriptorValue,
8
+ toggleDescriptorGroupValue,
9
+ } from './descriptorFields'
10
+
11
+ const field = (over: Partial<DescriptorField> & Pick<DescriptorField, 'key'>): DescriptorField => ({
12
+ label: over.key,
13
+ ...over,
14
+ })
15
+
16
+ describe('defaultDescriptorValues', () => {
17
+ it('seeds each declared default in its own contract shape', () => {
18
+ // The form model is the wire shape, so a default has to arrive typed: the shared validator
19
+ // refuses a `'3'` where a `number` field is declared, and would refuse it at create time too.
20
+ expect(
21
+ defaultDescriptorValues([
22
+ field({ key: 'style', type: 'select', default: 'collection' }),
23
+ field({ key: 'depth', type: 'number', default: '3' }),
24
+ field({ key: 'gate', type: 'checkbox', default: 'true' }),
25
+ field({ key: 'ops', type: 'checkbox-group', defaultValues: ['create', 'list'] }),
26
+ field({ key: 'dir', type: 'path', default: 'docs' }),
27
+ ]),
28
+ ).toEqual({
29
+ style: 'collection',
30
+ depth: 3,
31
+ gate: true,
32
+ ops: ['create', 'list'],
33
+ dir: 'docs',
34
+ })
35
+ })
36
+
37
+ it('leaves a field with no meaningful default ABSENT rather than blank', () => {
38
+ // Absent is what validation reads as unset, so seeding `''`/`[]`/`false` would both freeze an
39
+ // empty answer and (for a required field) look filled to nothing that checks it.
40
+ expect(
41
+ defaultDescriptorValues([
42
+ field({ key: 'entity', type: 'text' }),
43
+ field({ key: 'notes', type: 'textarea', default: '' }),
44
+ field({ key: 'gate', type: 'checkbox' }),
45
+ field({ key: 'gateOff', type: 'checkbox', default: 'false' }),
46
+ field({ key: 'ops', type: 'checkbox-group', defaultValues: [] }),
47
+ field({ key: 'depth', type: 'number', default: 'not-a-number' }),
48
+ ]),
49
+ ).toEqual({})
50
+ })
51
+
52
+ it('copies a multi-select default, so editing the form cannot mutate the descriptor', () => {
53
+ const ops = field({ key: 'ops', type: 'checkbox-group', defaultValues: ['create'] })
54
+ const seeded = defaultDescriptorValues([ops])
55
+ ;(seeded.ops as string[]).push('delete')
56
+ expect(ops.defaultValues).toEqual(['create'])
57
+ })
58
+ })
59
+
60
+ describe('setDescriptorValue', () => {
61
+ it('drops a value the shared rules read as unset rather than freezing it', () => {
62
+ // Absent is what `validateDescriptorFields` treats as unfilled and what
63
+ // `sanitizeDescriptorFields` refuses to freeze, so the form must not hold one either: a
64
+ // cleared field that stayed as `''`/`[]`/`false` would reach the wire as a collected answer.
65
+ expect(setDescriptorValue({ entity: 'Order' }, 'entity', '')).toEqual({})
66
+ expect(setDescriptorValue({ ops: ['create'] }, 'ops', [])).toEqual({})
67
+ expect(setDescriptorValue({ gate: true }, 'gate', false)).toEqual({})
68
+ expect(setDescriptorValue({ entity: 'Order' }, 'entity', undefined)).toEqual({})
69
+ })
70
+
71
+ it('keeps a numeric 0 but drops a half-typed number', () => {
72
+ // `0` is a real answer. `NaN` is what `Number('')`/a partial entry yields, and it serialises to
73
+ // `null` on the wire, which the value schema refuses: the submit would fail with a raw schema
74
+ // error naming nothing the user typed.
75
+ expect(setDescriptorValue({}, 'depth', 0)).toEqual({ depth: 0 })
76
+ expect(setDescriptorValue({ depth: 3 }, 'depth', Number.NaN)).toEqual({})
77
+ })
78
+
79
+ it('does not mutate the bag it was given', () => {
80
+ const before = { entity: 'Order' }
81
+ expect(setDescriptorValue(before, 'style', 'action')).toEqual({
82
+ entity: 'Order',
83
+ style: 'action',
84
+ })
85
+ expect(before).toEqual({ entity: 'Order' })
86
+ })
87
+ })
88
+
89
+ describe('setDescriptorCheckbox', () => {
90
+ it('persists an explicit false ONLY for a default-ON toggle', () => {
91
+ // A default-ON checkbox is the one field where absent and `false` are opposite facts: a
92
+ // consumer reads the opt-out as `inputs[key] !== false` (the tech-migration preset's
93
+ // `humanReview`), so dropping the `false` would make the toggle dead.
94
+ const onByDefault = field({ key: 'humanReview', type: 'checkbox', default: 'true' })
95
+ expect(setDescriptorCheckbox({}, onByDefault, false)).toEqual({ humanReview: false })
96
+ expect(setDescriptorCheckbox({}, onByDefault, true)).toEqual({ humanReview: true })
97
+ // A default-OFF checkbox never freezes a redundant `false`: absent already means unchecked.
98
+ const offByDefault = field({ key: 'breaking', type: 'checkbox' })
99
+ expect(setDescriptorCheckbox({ breaking: true }, offByDefault, false)).toEqual({})
100
+ })
101
+ })
102
+
103
+ describe('toggleDescriptorGroupValue', () => {
104
+ it('adds, removes and de-duplicates without freezing an empty selection', () => {
105
+ expect(toggleDescriptorGroupValue({}, 'ops', 'create', true)).toEqual({ ops: ['create'] })
106
+ expect(toggleDescriptorGroupValue({ ops: ['create'] }, 'ops', 'create', true)).toEqual({
107
+ ops: ['create'],
108
+ })
109
+ expect(toggleDescriptorGroupValue({ ops: ['create', 'list'] }, 'ops', 'create', false)).toEqual(
110
+ {
111
+ ops: ['list'],
112
+ },
113
+ )
114
+ // Unchecking the last option leaves the key ABSENT, not an empty array.
115
+ expect(toggleDescriptorGroupValue({ ops: ['create'] }, 'ops', 'create', false)).toEqual({})
116
+ })
117
+
118
+ it('reads a wrong-shaped stored value as an empty selection', () => {
119
+ // A bag can arrive from a probe prefill or a since-changed descriptor, so the reader narrows
120
+ // rather than assuming: a scalar under a multi-select key must not throw in the renderer.
121
+ expect(descriptorGroupValue({ ops: 'create' }, 'ops')).toEqual([])
122
+ expect(toggleDescriptorGroupValue({ ops: 'create' }, 'ops', 'list', true)).toEqual({
123
+ ops: ['list'],
124
+ })
125
+ })
126
+ })
@@ -0,0 +1,104 @@
1
+ import type { DescriptorField, DescriptorFieldValue, DescriptorFieldValues } from '~/types/domain'
2
+
3
+ // Form-side helpers over the shared descriptor-field vocabulary (`contracts/src/form-fields.ts`),
4
+ // used by every surface that renders one through `DescriptorFields.vue`: an initiative preset's
5
+ // create form and a reusable operation's per-case form on a custom task type.
6
+ //
7
+ // The RULES (visibility, validation, sanitization, prose rendering) live in contracts, because the
8
+ // server has to agree about them. What lives here is what only a FORM decides: which values to
9
+ // start it with, and how one edit changes the bag. Both are pure functions over the value bag
10
+ // rather than methods inside the SFC, so the mutation rules a wrong answer would freeze on an
11
+ // entity are unit-testable without mounting a component.
12
+
13
+ /**
14
+ * The initial, typed values a field list implies: its declared DEFAULTS folded into the
15
+ * `DescriptorFieldValues` shape the renderer and the wire contract expect (`checkbox-group` to
16
+ * `string[]`, `checkbox` to a boolean, `number` to a number, everything else a string). Only fields
17
+ * with a meaningful default are seeded, so an unfilled optional field stays absent (which is what
18
+ * validation reads as unset) and never freezes an empty value. A repo-detection probe's prefill and
19
+ * the user's own edits layer on top.
20
+ */
21
+ export function defaultDescriptorValues(fields: readonly DescriptorField[]): DescriptorFieldValues {
22
+ const values: DescriptorFieldValues = {}
23
+ for (const field of fields) {
24
+ if (field.type === 'checkbox-group') {
25
+ if (field.defaultValues?.length) values[field.key] = [...field.defaultValues]
26
+ } else if (field.type === 'checkbox') {
27
+ if (field.default === 'true') values[field.key] = true
28
+ } else if (field.type === 'number') {
29
+ const parsed = Number(field.default)
30
+ if (field.default !== undefined && field.default !== '' && Number.isFinite(parsed)) {
31
+ values[field.key] = parsed
32
+ }
33
+ } else if (field.default) {
34
+ values[field.key] = field.default
35
+ }
36
+ }
37
+ return values
38
+ }
39
+
40
+ /**
41
+ * A value that must stay ABSENT from the bag rather than freeze on the entity: an unchecked
42
+ * (`false`) checkbox, a blank string, an empty multi-select, or a number that is not one (a
43
+ * half-typed `number` input reads as `NaN`, which serialises to `null` on the wire and is refused
44
+ * by the value schema, so the form must never carry it). A numeric `0` is a real answer and is kept.
45
+ *
46
+ * The same judgement the shared `validateDescriptorFields` / `sanitizeDescriptorFields` make about
47
+ * an unset value, applied at the moment of the edit so the model never holds one at all.
48
+ */
49
+ function isEmptyDescriptorValue(value: DescriptorFieldValue): boolean {
50
+ if (typeof value === 'number') return !Number.isFinite(value)
51
+ return value === false || value === '' || (Array.isArray(value) && value.length === 0)
52
+ }
53
+
54
+ /**
55
+ * One field's value set immutably on the bag, DROPPING an empty one so a cleared field never
56
+ * freezes an empty `''`/`[]`/`false` (mirroring `ProviderConnectionTab`'s delete-when-blank).
57
+ */
58
+ export function setDescriptorValue(
59
+ values: DescriptorFieldValues,
60
+ key: string,
61
+ value: DescriptorFieldValue | undefined,
62
+ ): DescriptorFieldValues {
63
+ const next = { ...values }
64
+ if (value === undefined || isEmptyDescriptorValue(value)) delete next[key]
65
+ else next[key] = value
66
+ return next
67
+ }
68
+
69
+ /**
70
+ * A checkbox's value set on the bag. A checkbox whose descriptor default is ON (`default: 'true'`)
71
+ * must be able to persist an explicit `false`: {@link setDescriptorValue} otherwise drops it (an off
72
+ * box "stays unset"), which for a default-ON field is indistinguishable from "untouched, still on",
73
+ * so a consumer reading the opt-out as `humanReview !== false` (`seedMigrationPlan`) could never
74
+ * observe the unchecked state and the toggle would be dead. A default-OFF checkbox keeps the
75
+ * drop-when-false behaviour (absent === unchecked), so it never freezes a redundant `false`.
76
+ */
77
+ export function setDescriptorCheckbox(
78
+ values: DescriptorFieldValues,
79
+ field: DescriptorField,
80
+ checked: boolean,
81
+ ): DescriptorFieldValues {
82
+ if (!checked && field.default === 'true') return { ...values, [field.key]: false }
83
+ return setDescriptorValue(values, field.key, checked)
84
+ }
85
+
86
+ /** One `checkbox-group` field's current value, as the `string[]` the renderer and wire expect. */
87
+ export function descriptorGroupValue(values: DescriptorFieldValues, key: string): string[] {
88
+ const value = values[key]
89
+ return Array.isArray(value) ? value : []
90
+ }
91
+
92
+ /** One option toggled on/off in a `checkbox-group` field's value (deduped, order-preserving). */
93
+ export function toggleDescriptorGroupValue(
94
+ values: DescriptorFieldValues,
95
+ key: string,
96
+ option: string,
97
+ checked: boolean,
98
+ ): DescriptorFieldValues {
99
+ const current = descriptorGroupValue(values, key)
100
+ const next = checked
101
+ ? [...new Set([...current, option])]
102
+ : current.filter((entry) => entry !== option)
103
+ return setDescriptorValue(values, key, next)
104
+ }
@@ -4,8 +4,6 @@ import type {
4
4
  InitiativeItem,
5
5
  InitiativeItemStatus,
6
6
  InitiativePhase,
7
- InitiativePresetDescriptor,
8
- InitiativePresetInputs,
9
7
  InitiativeQa,
10
8
  InitiativeStatus,
11
9
  } from '~/types/domain'
@@ -140,34 +138,6 @@ export const INITIATIVE_FOLLOWUP_STATUS_CHIPS: Record<InitiativeFollowUp['status
140
138
  dismissed: 'neutral',
141
139
  }
142
140
 
143
- /**
144
- * The initial, typed create-form values a preset descriptor implies — its field DEFAULTS folded
145
- * into the `InitiativePresetInputs` shape the renderer + wire contract expect (`checkbox-group` →
146
- * `string[]`, `checkbox` → boolean, `number` → number, everything else a string). Only fields with
147
- * a meaningful default are seeded, so unfilled optional fields stay absent (equivalent to unset for
148
- * validation) and never freeze an empty value. The probe prefill and the user's edits layer on top.
149
- */
150
- export function defaultPresetInputs(
151
- descriptor: InitiativePresetDescriptor,
152
- ): InitiativePresetInputs {
153
- const inputs: InitiativePresetInputs = {}
154
- for (const field of descriptor.fields) {
155
- if (field.type === 'checkbox-group') {
156
- if (field.defaultValues?.length) inputs[field.key] = [...field.defaultValues]
157
- } else if (field.type === 'checkbox') {
158
- if (field.default === 'true') inputs[field.key] = true
159
- } else if (field.type === 'number') {
160
- const parsed = Number(field.default)
161
- if (field.default !== undefined && field.default !== '' && Number.isFinite(parsed)) {
162
- inputs[field.key] = parsed
163
- }
164
- } else if (field.default) {
165
- inputs[field.key] = field.default
166
- }
167
- }
168
- return inputs
169
- }
170
-
171
141
  /** Completion rollup across an initiative's items, or null when there are none. */
172
142
  export function initiativeProgress(
173
143
  items: InitiativeItem[] | undefined,
@@ -1,4 +1,4 @@
1
- // Which PRE-TOKEN INPUT GATE verdicts a run surfaces, and how they are presented.
1
+ // Which PRE-DISPATCH INPUT GATE verdicts a run surfaces, and how they are presented.
2
2
  //
3
3
  // The gate records a verdict for EVERY disposition, including the ones where it did nothing, so
4
4
  // "has a verdict" is not the same question as "has something to tell a human". This is the one
@@ -79,7 +79,7 @@ describe('dedicatedParkView', () => {
79
79
  expect(dedicatedParkView(step({}), run())).toBeNull()
80
80
  })
81
81
 
82
- // The PRE-TOKEN INPUT GATE parks whatever step 0 happens to be and leaves nothing
82
+ // The PRE-DISPATCH INPUT GATE parks whatever step 0 happens to be and leaves nothing
83
83
  // kind-specific on the step, so it is recognised off the RUN. The generic approve resolver
84
84
  // refuses it server-side: approving it would mark the run's first working step done and skip
85
85
  // the work the run exists to do.
@@ -150,7 +150,7 @@ export function dedicatedParkView(
150
150
  step: PipelineStep,
151
151
  instance: ExecutionInstance | null | undefined,
152
152
  ): 'follow-ups' | 'fork-decision' | 'input-gate' | null {
153
- // The PRE-TOKEN INPUT GATE parks whatever step 0 happens to be, so it leaves nothing on the
153
+ // The PRE-DISPATCH INPUT GATE parks whatever step 0 happens to be, so it leaves nothing on the
154
154
  // STEP to recognise it by: its verdict is a fact about the RUN. Checked first, and off the
155
155
  // instance: approving it generically would mark the run's first working step done and skip
156
156
  // the work the run exists to do.
@@ -2671,6 +2671,7 @@
2671
2671
  "continue": "Weiter",
2672
2672
  "submit": "Aufgabe hinzufügen",
2673
2673
  "addFailedTitle": "Aufgabe konnte nicht hinzugefügt werden",
2674
+ "customFieldsInvalid": "Das Formular dieses Aufgabentyps hat sich seit dem Öffnen geändert, daher passen die Antworten nicht mehr zu den Angaben, die es verlangt. Schließen Sie den Dialog und öffnen Sie ihn erneut, um die aktuellen Felder auszufüllen.",
2674
2675
  "linkFailed": "Aufgabe hinzugefügt, aber {count} Anhang konnte nicht verknüpft werden | Aufgabe hinzugefügt, aber {count} Anhänge konnten nicht verknüpft werden",
2675
2676
  "review": {
2676
2677
  "prUrl": "Pull Request",
@@ -4814,7 +4815,6 @@
4814
4815
  "titlePlaceholder": "z. B. Die API auf das neue Auth-Modell migrieren",
4815
4816
  "goalField": "Ziel",
4816
4817
  "goalPlaceholder": "Beschreiben Sie das Ziel, die Einschränkungen und den groben Umfang. Der Planer verfeinert dies zu einem mehrphasigen Plan.",
4817
- "pathInvalid": "Geben Sie einen Pfad innerhalb des Repositorys an (kein \"..\", keine absoluten Pfade und keine Backslashes).",
4818
4818
  "hint": "Es läuft noch nichts: Führen Sie nach dem Erstellen die Initiative-Planning-Pipeline auf dem Block aus. Sie analysiert die Codebasis und entwirft den mehrphasigen Plan zu Ihrer Freigabe.",
4819
4819
  "submit": "Initiative erstellen",
4820
4820
  "failedTitle": "Die Initiative konnte nicht erstellt werden",
@@ -5027,7 +5027,8 @@
5027
5027
  "confirm": "Verwerfen",
5028
5028
  "keep": "Weiter bearbeiten"
5029
5029
  },
5030
- "edit": "Bearbeiten"
5030
+ "edit": "Bearbeiten",
5031
+ "pathInvalid": "Geben Sie einen Pfad innerhalb des Repositorys an (kein \"..\", keine absoluten Pfade und keine Backslashes)."
5031
5032
  },
5032
5033
  "access": {
5033
5034
  "noBoardWrite": "Nur-Lese-Zugriff: Sie können dieses Board ansehen, aber nicht bearbeiten.",
@@ -98,7 +98,8 @@
98
98
  "confirm": "Discard",
99
99
  "keep": "Keep editing"
100
100
  },
101
- "edit": "Edit"
101
+ "edit": "Edit",
102
+ "pathInvalid": "Enter a path inside the repository (no \"..\", absolute paths, or backslashes)."
102
103
  },
103
104
  "access": {
104
105
  "noBoardWrite": "Read-only access: you can view this board but can't edit it.",
@@ -325,6 +326,10 @@
325
326
  "continue": "Continue",
326
327
  "submit": "Add task",
327
328
  "addFailedTitle": "Could not add task",
329
+ "customFieldsInvalid": "This task type's form has changed since you opened it, so the answers no longer match what it asks for. Close and reopen the dialog to fill in the current fields.",
330
+ "@customFieldsInvalid": {
331
+ "description": "Shown when the server refuses a created task because the collected values for a deployment-registered custom task type contradict the type's declared form (a required answer missing, a value outside its options). In practice this means the deployment re-registered the type while the dialog was open."
332
+ },
328
333
  "linkFailed": "Task added, but {count} attachment could not be linked | Task added, but {count} attachments could not be linked",
329
334
  "@linkFailed": {
330
335
  "description": "Count-based: how many context attachments (docs/issues) failed to link after the task was created (count is always >= 1). Provide ALL plural forms your language needs (English has 2; Polish/Ukrainian need 3 - one/few/many - via the custom pluralRules in i18n.config.ts)."
@@ -6137,7 +6142,6 @@
6137
6142
  "titlePlaceholder": "e.g. Migrate the API to the new auth model",
6138
6143
  "goalField": "Goal",
6139
6144
  "goalPlaceholder": "Describe the goal, constraints and rough scope. The planner refines this into a multi-phase plan.",
6140
- "pathInvalid": "Enter a path inside the repository (no \"..\", absolute paths, or backslashes).",
6141
6145
  "hint": "Nothing runs yet: after creating, run the Initiative Planning pipeline on the block. It analyses the codebase and drafts the multi-phase plan for your approval.",
6142
6146
  "submit": "Create initiative",
6143
6147
  "failedTitle": "Could not create the initiative",
@@ -83,7 +83,8 @@
83
83
  "confirm": "Descartar",
84
84
  "keep": "Seguir editando"
85
85
  },
86
- "edit": "Editar"
86
+ "edit": "Editar",
87
+ "pathInvalid": "Introduce una ruta dentro del repositorio (sin \"..\", rutas absolutas ni barras invertidas)."
87
88
  },
88
89
  "access": {
89
90
  "noBoardWrite": "Acceso de solo lectura: puedes ver este tablero pero no editarlo.",
@@ -298,6 +299,7 @@
298
299
  "continue": "Continuar",
299
300
  "submit": "Añadir tarea",
300
301
  "addFailedTitle": "No se pudo añadir la tarea",
302
+ "customFieldsInvalid": "El formulario de este tipo de tarea ha cambiado desde que lo abriste, por lo que las respuestas ya no coinciden con lo que pide. Cierra y vuelve a abrir el diálogo para rellenar los campos actuales.",
301
303
  "linkFailed": "Tarea añadida, pero no se pudo vincular {count} adjunto | Tarea añadida, pero no se pudieron vincular {count} adjuntos",
302
304
  "review": {
303
305
  "prUrl": "Pull request",
@@ -3224,13 +3226,25 @@
3224
3226
  "executionBackend": {
3225
3227
  "label": "Dónde se ejecutan los agentes",
3226
3228
  "local-docker": "Docker local (host)",
3229
+ "local-dockerDesc": "Ejecuta cada agente en un contenedor sobre el daemon de Docker de esta máquina.",
3227
3230
  "cloudflare-containers": "Cloudflare Containers (integrado)",
3228
- "runner-pool": "Pool de ejecutores autoalojado"
3231
+ "cloudflare-containersDesc": "Ejecuta los agentes en la plataforma de contenedores por ejecución integrada de Cloudflare.",
3232
+ "kubernetes": "Clúster de Kubernetes",
3233
+ "kubernetesDesc": "Ejecuta cada agente como un pod en un clúster de Kubernetes que tú gestionas.",
3234
+ "runner-pool": "Pool de ejecutores personalizado (HTTP)",
3235
+ "runner-poolDesc": "Envía los agentes a tu propio planificador detrás de una API de manifiestos HTTP.",
3236
+ "k3s": "Kubernetes local (k3s)",
3237
+ "k3sDesc": "Precargado para un clúster k3s/k3d local en esta máquina: solo tienes que pegar un token."
3229
3238
  },
3230
3239
  "testEnvBackend": {
3231
3240
  "label": "Dónde se ejecutan los entornos de prueba",
3232
- "local-compose": "docker-compose en contenedor",
3233
- "environment-provider": "Proveedor de entornos",
3241
+ "local-compose": "docker-compose en contenedor (dependencias del Tester)",
3242
+ "local-composeDesc": "Levanta las dependencias del Tester con docker-compose dentro del contenedor de la ejecución. Para un entorno de vista previa completo de la app, usa la opción de entorno de vista previa de Docker Compose de abajo.",
3243
+ "kubernetes": "Clúster de Kubernetes",
3244
+ "kubernetesDesc": "Aprovisiona un namespace por PR en un clúster de Kubernetes que tú gestionas. Ideal para apps nativas de Kubernetes o si ya tienes un clúster en marcha.",
3245
+ "environment-provider": "Proveedor HTTP personalizado",
3246
+ "environment-providerDesc": "Aprovisiona entornos efímeros a través de tu propia API de gestión HTTP. Ideal si ya cuentas con herramientas propias de entornos de vista previa.",
3247
+ "compose": "Entorno de vista previa de Docker Compose",
3234
3248
  "composeDesc": "Levanta el docker-compose.yml del propio repositorio en un daemon de Docker local como URL del Tester. Ideal para apps locales basadas en Compose. Admite imágenes prediseñadas o compilación desde el código fuente; requiere un daemon de Docker, por lo que solo en despliegues locales."
3235
3249
  },
3236
3250
  "dockerComposeInfo": "Los servicios de Docker Compose se levantan en el Docker local del runtime: no se necesita conexión (el contenedor de la ejecución debe admitir Docker-in-Docker). Configura el servicio, el puerto y el origen de la imagen (usar prediseñada o compilar desde el código fuente) en los ajustes de entorno del servicio.",
@@ -5930,7 +5944,6 @@
5930
5944
  "titlePlaceholder": "p. ej. Migrar la API al nuevo modelo de autenticacion",
5931
5945
  "goalField": "Objetivo",
5932
5946
  "goalPlaceholder": "Describe el objetivo, las restricciones y el alcance aproximado. El planificador lo refina en un plan multifase.",
5933
- "pathInvalid": "Introduce una ruta dentro del repositorio (sin \"..\", rutas absolutas ni barras invertidas).",
5934
5947
  "hint": "Todavia no se ejecuta nada: tras crearla, ejecuta el pipeline de planificacion de iniciativas sobre el bloque. Analiza el codigo y redacta el plan multifase para tu aprobacion.",
5935
5948
  "submit": "Crear iniciativa",
5936
5949
  "failedTitle": "No se pudo crear la iniciativa",
@@ -83,7 +83,8 @@
83
83
  "confirm": "Ignorer",
84
84
  "keep": "Continuer l'édition"
85
85
  },
86
- "edit": "Modifier"
86
+ "edit": "Modifier",
87
+ "pathInvalid": "Saisissez un chemin a l'interieur du depot (pas de \"..\", de chemins absolus ni d'antislashs)."
87
88
  },
88
89
  "access": {
89
90
  "noBoardWrite": "Accès en lecture seule : vous pouvez consulter ce tableau mais pas le modifier.",
@@ -298,6 +299,7 @@
298
299
  "continue": "Continuer",
299
300
  "submit": "Ajouter la tâche",
300
301
  "addFailedTitle": "Impossible d’ajouter la tâche",
302
+ "customFieldsInvalid": "Le formulaire de ce type de tâche a changé depuis son ouverture : les réponses ne correspondent donc plus à ce qui est demandé. Fermez puis réouvrez la boîte de dialogue pour remplir les champs actuels.",
301
303
  "linkFailed": "Tâche ajoutée, mais {count} pièce jointe n’a pas pu être liée | Tâche ajoutée, mais {count} pièces jointes n’ont pas pu être liées",
302
304
  "review": {
303
305
  "prUrl": "Pull request",
@@ -3224,13 +3226,25 @@
3224
3226
  "executionBackend": {
3225
3227
  "label": "Où s'exécutent les agents",
3226
3228
  "local-docker": "Docker local (hôte)",
3229
+ "local-dockerDesc": "Exécute chaque agent dans un conteneur sur le démon Docker de cette machine.",
3227
3230
  "cloudflare-containers": "Cloudflare Containers (intégré)",
3228
- "runner-pool": "Pool d'exécuteurs auto-hébergé"
3231
+ "cloudflare-containersDesc": "Exécute les agents sur la plateforme de conteneurs par exécution intégrée à Cloudflare.",
3232
+ "kubernetes": "Cluster Kubernetes",
3233
+ "kubernetesDesc": "Exécute chaque agent en tant que pod dans un cluster Kubernetes que vous gérez.",
3234
+ "runner-pool": "Pool d'exécuteurs personnalisé (HTTP)",
3235
+ "runner-poolDesc": "Envoie les agents vers votre propre ordonnanceur derrière une API de manifestes HTTP.",
3236
+ "k3s": "Kubernetes local (k3s)",
3237
+ "k3sDesc": "Préconfiguré pour un cluster k3s/k3d local sur cette machine : il suffit de coller un jeton."
3229
3238
  },
3230
3239
  "testEnvBackend": {
3231
3240
  "label": "Où s'exécutent les environnements de test",
3232
- "local-compose": "docker-compose dans le conteneur",
3233
- "environment-provider": "Fournisseur d'environnements",
3241
+ "local-compose": "docker-compose dans le conteneur (dépendances du Testeur)",
3242
+ "local-composeDesc": "Démarre les dépendances du Testeur avec docker-compose à l'intérieur du conteneur de l'exécution. Pour un environnement de prévisualisation complet de l'application, utilisez l'option d'environnement de prévisualisation Docker Compose ci-dessous.",
3243
+ "kubernetes": "Cluster Kubernetes",
3244
+ "kubernetesDesc": "Provisionne un namespace par PR dans un cluster Kubernetes que vous gérez. Idéal pour les applications natives Kubernetes ou si vous exploitez déjà un cluster.",
3245
+ "environment-provider": "Fournisseur HTTP personnalisé",
3246
+ "environment-providerDesc": "Provisionne des environnements éphémères via votre propre API de gestion HTTP. Idéal si vous disposez déjà d'un outillage dédié aux environnements de prévisualisation.",
3247
+ "compose": "Environnement de prévisualisation Docker Compose",
3234
3248
  "composeDesc": "Démarre le docker-compose.yml du dépôt sur un démon Docker local comme URL du Testeur. Idéal pour les applications locales basées sur Compose. Prend en charge les images préconstruites ou la construction depuis les sources ; nécessite un démon Docker, donc uniquement pour les déploiements locaux."
3235
3249
  },
3236
3250
  "dockerComposeInfo": "Les services Docker Compose démarrent sur le Docker local du runtime : aucune connexion requise (le conteneur d’exécution doit prendre en charge Docker-in-Docker). Configurez le service, le port et la source de l’image (image préconstruite ou construction depuis les sources) dans les paramètres d’environnement du service.",
@@ -5930,7 +5944,6 @@
5930
5944
  "titlePlaceholder": "p. ex. Migrer l'API vers le nouveau modele d'authentification",
5931
5945
  "goalField": "Objectif",
5932
5946
  "goalPlaceholder": "Decrivez l'objectif, les contraintes et le perimetre approximatif. Le planificateur l'affine en un plan multiphase.",
5933
- "pathInvalid": "Saisissez un chemin a l'interieur du depot (pas de \"..\", de chemins absolus ni d'antislashs).",
5934
5947
  "hint": "Rien ne s'execute encore : apres la creation, lancez le pipeline de planification d'initiative sur le bloc. Il analyse le code et redige le plan multiphase pour votre approbation.",
5935
5948
  "submit": "Creer l'initiative",
5936
5949
  "failedTitle": "Impossible de creer l'initiative",
@@ -83,7 +83,8 @@
83
83
  "confirm": "לבטל",
84
84
  "keep": "להמשיך לערוך"
85
85
  },
86
- "edit": "עריכה"
86
+ "edit": "עריכה",
87
+ "pathInvalid": "הזינו נתיב בתוך המאגר (ללא \"..\", נתיבים מוחלטים או קו נטוי הפוך)."
87
88
  },
88
89
  "access": {
89
90
  "noBoardWrite": "גישת קריאה בלבד: אפשר לצפות בלוח הזה אך לא לערוך אותו.",
@@ -298,6 +299,7 @@
298
299
  "continue": "המשך",
299
300
  "submit": "הוסף משימה",
300
301
  "addFailedTitle": "לא ניתן היה להוסיף משימה",
302
+ "customFieldsInvalid": "הטופס של סוג המשימה הזה השתנה מאז שפתחתם אותו, ולכן התשובות אינן תואמות עוד את מה שהוא מבקש. סגרו ופתחו מחדש את החלון כדי למלא את השדות הנוכחיים.",
301
303
  "linkFailed": "המשימה נוספה, אך {count} צרופה לא ניתנה לקישור | המשימה נוספה, אך {count} צרופות לא ניתנו לקישור",
302
304
  "review": {
303
305
  "prUrl": "בקשת משיכה",
@@ -2460,6 +2462,7 @@
2460
2462
  "kubernetesDesc": "הקצה מרחב שמות לכל PR באשכול Kubernetes שאתה מפעיל.",
2461
2463
  "environment-provider": "ספק HTTP מותאם",
2462
2464
  "environment-providerDesc": "הקצה סביבות ארעיות דרך ממשק ניהול HTTP משלך.",
2465
+ "compose": "סביבת תצוגה מקדימה של Docker Compose",
2463
2466
  "composeDesc": "מריץ את קובץ docker-compose.yml של המאגר עצמו על דימון Docker מקומי ככתובת ה-URL של הבוחן. מתאים בעיקר לאפליקציות מקומיות מבוססות Compose. תומך בתמונות בנויות מראש או בבנייה מקוד המקור; דורש דימון Docker, ולכן פריסות מקומיות בלבד."
2464
2467
  },
2465
2468
  "dockerComposeInfo": "שירותי Docker Compose עולים על ה-Docker המקומי של סביבת הריצה: אין צורך בחיבור (מכולת ההרצה חייבת לתמוך ב-Docker-in-Docker). הגדר את השירות, הפורט ומקור התמונה (משיכת תמונה בנויה מראש או בנייה מקוד המקור) בהגדרות הסביבה של השירות.",
@@ -5941,7 +5944,6 @@
5941
5944
  "titlePlaceholder": "לדוגמה: העברת ה-API למודל האימות החדש",
5942
5945
  "goalField": "מטרה",
5943
5946
  "goalPlaceholder": "תארו את המטרה, המגבלות והיקף משוער. המתכנן מזקק זאת לתוכנית רב-שלבית.",
5944
- "pathInvalid": "הזינו נתיב בתוך המאגר (ללא \"..\", נתיבים מוחלטים או קו נטוי הפוך).",
5945
5947
  "hint": "שום דבר לא רץ עדיין: לאחר היצירה, הריצו את צינור תכנון היוזמה על הבלוק. הוא מנתח את הקוד ומנסח את התוכנית הרב-שלבית לאישורכם.",
5946
5948
  "submit": "יצירת יוזמה",
5947
5949
  "failedTitle": "לא ניתן היה ליצור את היוזמה",
@@ -2671,6 +2671,7 @@
2671
2671
  "continue": "Continua",
2672
2672
  "submit": "Aggiungi attività",
2673
2673
  "addFailedTitle": "Impossibile aggiungere l'attività",
2674
+ "customFieldsInvalid": "Il modulo di questo tipo di attività è cambiato da quando lo hai aperto, quindi le risposte non corrispondono più a quanto richiesto. Chiudi e riapri la finestra per compilare i campi attuali.",
2674
2675
  "linkFailed": "Attività aggiunta, ma {count} allegato non è stato collegato | Attività aggiunta, ma {count} allegati non sono stati collegati",
2675
2676
  "review": {
2676
2677
  "prUrl": "Pull request",
@@ -4814,7 +4815,6 @@
4814
4815
  "titlePlaceholder": "es. Migrare l'API al nuovo modello di autenticazione",
4815
4816
  "goalField": "Obiettivo",
4816
4817
  "goalPlaceholder": "Descrivi l'obiettivo, i vincoli e l'ambito approssimativo. Il pianificatore lo affina in un piano multi-fase.",
4817
- "pathInvalid": "Inserisci un percorso all'interno del repository (senza \"..\", percorsi assoluti o barre rovesciate).",
4818
4818
  "hint": "Non viene eseguito ancora nulla: dopo la creazione, esegui la pipeline di Pianificazione dell'iniziativa sul blocco. Analizza il codebase e redige il piano multi-fase per la tua approvazione.",
4819
4819
  "submit": "Crea iniziativa",
4820
4820
  "failedTitle": "Impossibile creare l'iniziativa",
@@ -5027,7 +5027,8 @@
5027
5027
  "confirm": "Ignora",
5028
5028
  "keep": "Continua a modificare"
5029
5029
  },
5030
- "edit": "Modifica"
5030
+ "edit": "Modifica",
5031
+ "pathInvalid": "Inserisci un percorso all'interno del repository (senza \"..\", percorsi assoluti o barre rovesciate)."
5031
5032
  },
5032
5033
  "access": {
5033
5034
  "noBoardWrite": "Accesso in sola lettura: puoi visualizzare questa board ma non modificarla.",
@@ -83,7 +83,8 @@
83
83
  "confirm": "破棄",
84
84
  "keep": "編集を続ける"
85
85
  },
86
- "edit": "編集"
86
+ "edit": "編集",
87
+ "pathInvalid": "リポジトリ内のパスを入力してください(\"..\"、絶対パス、バックスラッシュは使用できません)。"
87
88
  },
88
89
  "access": {
89
90
  "noBoardWrite": "読み取り専用アクセス: このボードは閲覧できますが編集はできません。",
@@ -298,6 +299,7 @@
298
299
  "continue": "続行",
299
300
  "submit": "タスクを追加",
300
301
  "addFailedTitle": "タスクを追加できませんでした",
302
+ "customFieldsInvalid": "このタスクタイプのフォームは開いたあとに変更されたため、入力内容が求められているものと一致しません。ダイアログを閉じて開き直し、現在の項目に入力してください。",
301
303
  "linkFailed": "タスクを追加しましたが、{count} 件の添付をリンクできませんでした | タスクを追加しましたが、{count} 件の添付をリンクできませんでした",
302
304
  "review": {
303
305
  "prUrl": "プルリクエスト",
@@ -5942,7 +5944,6 @@
5942
5944
  "titlePlaceholder": "例: API を新しい認証モデルへ移行する",
5943
5945
  "goalField": "ゴール",
5944
5946
  "goalPlaceholder": "ゴール、制約、おおまかなスコープを記述してください。プランナーが複数フェーズの計画に練り上げます。",
5945
- "pathInvalid": "リポジトリ内のパスを入力してください(\"..\"、絶対パス、バックスラッシュは使用できません)。",
5946
5947
  "hint": "この時点では何も実行されません。作成後、このブロックでイニシアチブ計画パイプラインを実行してください。コードベースを分析し、承認用の複数フェーズ計画を起草します。",
5947
5948
  "submit": "イニシアチブを作成",
5948
5949
  "failedTitle": "イニシアチブを作成できませんでした",
@@ -83,7 +83,8 @@
83
83
  "confirm": "Odrzuć",
84
84
  "keep": "Kontynuuj edycję"
85
85
  },
86
- "edit": "Edytuj"
86
+ "edit": "Edytuj",
87
+ "pathInvalid": "Podaj sciezke wewnatrz repozytorium (bez \"..\", sciezek bezwzglednych ani ukosnikow wstecznych)."
87
88
  },
88
89
  "access": {
89
90
  "noBoardWrite": "Dostęp tylko do odczytu: możesz przeglądać tę tablicę, ale nie możesz jej edytować.",
@@ -298,6 +299,7 @@
298
299
  "continue": "Dalej",
299
300
  "submit": "Dodaj zadanie",
300
301
  "addFailedTitle": "Nie udało się dodać zadania",
302
+ "customFieldsInvalid": "Formularz tego typu zadania zmienił się od chwili otwarcia, więc odpowiedzi nie odpowiadają już temu, o co pyta. Zamknij i otwórz okno ponownie, aby wypełnić aktualne pola.",
301
303
  "linkFailed": "Zadanie dodane, ale nie udało się powiązać {count} załącznika | Zadanie dodane, ale nie udało się powiązać {count} załączników | Zadanie dodane, ale nie udało się powiązać {count} załączników",
302
304
  "review": {
303
305
  "prUrl": "Pull request",
@@ -3224,13 +3226,25 @@
3224
3226
  "executionBackend": {
3225
3227
  "label": "Gdzie działają agenci",
3226
3228
  "local-docker": "Lokalny Docker (host)",
3229
+ "local-dockerDesc": "Uruchamia każdego agenta w kontenerze na demonie Dockera tej maszyny.",
3227
3230
  "cloudflare-containers": "Cloudflare Containers (wbudowane)",
3228
- "runner-pool": "Samodzielnie hostowana pula runnerów"
3231
+ "cloudflare-containersDesc": "Uruchamia agentów na wbudowanej platformie kontenerów Cloudflare, po jednym na uruchomienie.",
3232
+ "kubernetes": "Klaster Kubernetes",
3233
+ "kubernetesDesc": "Uruchamia każdego agenta jako pod w klastrze Kubernetes, którym sam zarządzasz.",
3234
+ "runner-pool": "Własna pula runnerów (HTTP)",
3235
+ "runner-poolDesc": "Kieruje agentów do własnego harmonogramu za API manifestów HTTP.",
3236
+ "k3s": "Lokalny Kubernetes (k3s)",
3237
+ "k3sDesc": "Wypełnione wstępnie dla lokalnego klastra k3s/k3d na tej maszynie: wystarczy wkleić token."
3229
3238
  },
3230
3239
  "testEnvBackend": {
3231
3240
  "label": "Gdzie działają środowiska testowe",
3232
- "local-compose": "docker-compose w kontenerze",
3233
- "environment-provider": "Dostawca środowisk",
3241
+ "local-compose": "docker-compose w kontenerze (zależności Testera)",
3242
+ "local-composeDesc": "Uruchamia zależności Testera przez docker-compose wewnątrz kontenera uruchomienia. Pełne środowisko podglądu aplikacji znajdziesz w opcji środowiska podglądu Docker Compose poniżej.",
3243
+ "kubernetes": "Klaster Kubernetes",
3244
+ "kubernetesDesc": "Tworzy przestrzeń nazw dla każdego PR w klastrze Kubernetes, którym sam zarządzasz. Najlepsze dla aplikacji natywnych dla Kubernetes lub gdy już masz klaster.",
3245
+ "environment-provider": "Własny dostawca HTTP",
3246
+ "environment-providerDesc": "Tworzy efemeryczne środowiska przez własne API zarządzania HTTP. Najlepsze, gdy masz już własne narzędzia do środowisk podglądu.",
3247
+ "compose": "Środowisko podglądu Docker Compose",
3234
3248
  "composeDesc": "Uruchamia własny plik docker-compose.yml repozytorium na lokalnym demonie Dockera jako adres URL Testera. Najlepsze dla lokalnych aplikacji opartych na Compose. Obsługuje gotowe obrazy lub budowanie ze źródeł; wymaga demona Dockera, więc tylko wdrożenia lokalne."
3235
3249
  },
3236
3250
  "dockerComposeInfo": "Usługi Docker Compose uruchamiają się na lokalnym Dockerze środowiska uruchomieniowego: połączenie nie jest potrzebne (kontener uruchomienia musi obsługiwać Docker-in-Docker). Skonfiguruj usługę, port i źródło obrazu (pobranie gotowego lub budowanie ze źródeł) w ustawieniach środowiska usługi.",
@@ -5930,7 +5944,6 @@
5930
5944
  "titlePlaceholder": "np. Migracja API do nowego modelu uwierzytelniania",
5931
5945
  "goalField": "Cel",
5932
5946
  "goalPlaceholder": "Opisz cel, ograniczenia i przyblizony zakres. Planista doprecyzuje to w wielofazowy plan.",
5933
- "pathInvalid": "Podaj sciezke wewnatrz repozytorium (bez \"..\", sciezek bezwzglednych ani ukosnikow wstecznych).",
5934
5947
  "hint": "Nic jeszcze nie jest uruchamiane: po utworzeniu uruchom na bloku pipeline planowania inicjatywy. Analizuje on kod i przygotowuje wielofazowy plan do zatwierdzenia.",
5935
5948
  "submit": "Utworz inicjatywe",
5936
5949
  "failedTitle": "Nie udalo sie utworzyc inicjatywy",