@cat-factory/app 0.201.1 → 0.204.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/README.md +130 -10
  2. package/app/components/binaryOutput/BinaryOutputReport.vue +186 -0
  3. package/app/components/initiative/InitiativePlanReview.vue +11 -1
  4. package/app/components/panels/AgentStepDetail.vue +10 -0
  5. package/app/components/panels/ResultWindowShell.vue +86 -0
  6. package/app/components/pipeline/BinaryOutputStepPicker.vue +147 -0
  7. package/app/components/pipeline/PipelineBuilder.vue +54 -0
  8. package/app/components/settings/OpenRouterCatalogPanel.vue +6 -3
  9. package/app/components/tutorial/TutorialCatalogue.logic.spec.ts +103 -0
  10. package/app/components/tutorial/TutorialCatalogue.logic.ts +102 -0
  11. package/app/components/tutorial/TutorialCatalogue.vue +150 -0
  12. package/app/components/tutorial/TutorialOverlay.logic.spec.ts +46 -0
  13. package/app/components/tutorial/TutorialOverlay.logic.ts +53 -0
  14. package/app/components/tutorial/TutorialOverlay.vue +296 -40
  15. package/app/components/tutorial/TutorialPrompt.vue +41 -22
  16. package/app/composables/useNavContributions.ts +4 -1
  17. package/app/composables/useTutorialLaunch.ts +50 -0
  18. package/app/composables/useTutorialTours.ts +37 -9
  19. package/app/docs/consumer-extensions.md +24 -11
  20. package/app/modular/agent-kinds.ts +6 -0
  21. package/app/modular/nav-contributions.spec.ts +7 -0
  22. package/app/modular/nav-contributions.ts +25 -13
  23. package/app/modular/slots.ts +5 -2
  24. package/app/modular/tutorial-tours.spec.ts +189 -53
  25. package/app/modular/tutorial-tours.ts +57 -8
  26. package/app/pages/index.vue +7 -2
  27. package/app/stores/pipelines/draftBinaryOutput.spec.ts +70 -0
  28. package/app/stores/pipelines/draftStepConfig.ts +38 -2
  29. package/app/stores/tutorial.spec.ts +167 -0
  30. package/app/stores/tutorial.ts +140 -4
  31. package/app/types/domain.ts +9 -0
  32. package/app/types/execution.ts +5 -0
  33. package/app/utils/binaryOutput.spec.ts +307 -0
  34. package/app/utils/binaryOutput.ts +343 -0
  35. package/app/utils/tutorial.spec.ts +179 -9
  36. package/app/utils/tutorial.ts +233 -22
  37. package/i18n/locales/de.json +89 -7
  38. package/i18n/locales/en.json +101 -7
  39. package/i18n/locales/es.json +89 -7
  40. package/i18n/locales/fr.json +89 -7
  41. package/i18n/locales/he.json +89 -7
  42. package/i18n/locales/it.json +89 -7
  43. package/i18n/locales/ja.json +89 -7
  44. package/i18n/locales/pl.json +89 -7
  45. package/i18n/locales/tr.json +89 -7
  46. package/i18n/locales/uk.json +89 -7
  47. package/package.json +2 -2
@@ -95,6 +95,30 @@ export interface TutorialStep {
95
95
  when?: (gates: NavGates) => boolean
96
96
  }
97
97
 
98
+ /**
99
+ * One named precondition a tour needs before it can be taken — a board write permission, a
100
+ * source-control connection, a service on the board.
101
+ *
102
+ * A DECLARED object rather than the bare `when(gates)` predicate a tour used to carry, because
103
+ * the catalogue has to say why a tour it is showing cannot be started right now. A predicate
104
+ * answers only "no", and a surface that lists what it can offer while silently omitting the
105
+ * rest reads exactly like a deployment that ships four tours instead of six — the "absent and
106
+ * zero must never render the same" rule, applied to a catalog. Pairing the predicate with the
107
+ * i18n key that NAMES it means the reason is computed from the same fact that withheld the
108
+ * tour, rather than restated beside it and left to drift.
109
+ *
110
+ * A consumer deployment writes its own: this is a plain object with its own copy key, so it
111
+ * needs no registration and no entry in a first-party table.
112
+ */
113
+ export interface TutorialRequirement {
114
+ /** Stable id, unique within a tour's list; the catalogue keys its reason list on it. */
115
+ id: string
116
+ /** i18n key naming the requirement as a noun phrase ("A service on the board"). */
117
+ labelKey: string
118
+ /** Whether this board/user currently satisfies it. */
119
+ met: (gates: NavGates) => boolean
120
+ }
121
+
98
122
  export interface TutorialTour {
99
123
  /** Stable id; completion is persisted against it, so renaming one resets its state. */
100
124
  id: string
@@ -104,48 +128,198 @@ export interface TutorialTour {
104
128
  /** Sort key in the tour list; ties break on `id` so the order is deterministic. */
105
129
  order: number
106
130
  /**
107
- * Availability gate over the same reactive {@link NavGates} the nav catalog uses, so a
108
- * tour about a surface the caller can't reach (no board write, no GitHub) never shows.
109
- * Absent = always offered.
131
+ * What this board/user must have before the tour can run, over the same reactive
132
+ * {@link NavGates} the nav catalog uses — so a tour about a surface the caller can't reach
133
+ * (no board write, no source control) is never started, and the catalogue can say which of
134
+ * these is the one still missing. Absent = always available.
110
135
  */
111
- when?: (gates: NavGates) => boolean
136
+ requires?: readonly TutorialRequirement[]
112
137
  steps: readonly TutorialStep[]
113
138
  }
114
139
 
115
140
  /** How long the overlay polls for a step's anchor before auto-skipping the step. */
116
141
  export const DEFAULT_TARGET_WAIT_MS = 4000
117
142
 
118
- /** How often the overlay re-queries / re-measures its anchor (canvas pans, panels open). */
143
+ /**
144
+ * How often the overlay re-queries the DOM while it is still HUNTING for a step's anchor.
145
+ * Fast on purpose — the anchor can appear at any moment (a modal mounting, a live event
146
+ * landing a card) and every tick spent waiting is time the user stares at a "looking for
147
+ * it" note — and bounded on purpose, by the step's own {@link DEFAULT_TARGET_WAIT_MS}-ish
148
+ * budget, after which the step is skipped and the hunt stops.
149
+ */
119
150
  export const TARGET_TRACK_INTERVAL_MS = 150
120
151
 
152
+ /**
153
+ * How often the overlay re-queries once it HAS an anchor.
154
+ *
155
+ * Anchored tracking is event-driven (scroll, resize, element resize, canvas pan/zoom), so
156
+ * this interval is only the backstop for movement nothing reports — and it is the tick that
157
+ * runs for the whole length of a tour, where the hunting one above is bounded by a step's
158
+ * wait budget. It also re-RESOLVES the selector rather than re-measuring the cached element,
159
+ * which is what lets a step re-anchor when the control it names is replaced underneath it.
160
+ */
161
+ export const TARGET_IDLE_INTERVAL_MS = 400
162
+
163
+ /**
164
+ * How much of an anchor has to be on screen before the overlay leaves the viewport alone.
165
+ *
166
+ * Measured against `min(anchorArea, viewportArea)`, not against the anchor's own area, because
167
+ * the catalog points at controls of wildly different sizes: `add-task-submit` is a button that
168
+ * must be almost wholly visible to be pointed at, while `board-canvas` and `sidebar` are bigger
169
+ * than the viewport and can NEVER clear a fraction of their own area. Taking the smaller of the
170
+ * two means "mostly visible" for a small control and "filling a good part of the screen" for a
171
+ * large one, which is the same judgement in both cases.
172
+ */
173
+ export const MIN_VISIBLE_RATIO = 0.5
174
+
121
175
  /** Deterministic tour-list order: `order`, then `id`. */
122
176
  export function sortTours(tours: readonly TutorialTour[]): TutorialTour[] {
123
177
  return [...tours].sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
124
178
  }
125
179
 
126
180
  /**
127
- * Resolve every tour against the gates: drop the tours whose own `when` rejects them, drop
128
- * the steps whose `when` rejects them, and then drop a tour left with no steps at all.
181
+ * Why a tour is or isn't offered right now.
129
182
  *
130
- * That last rule is what makes per-step gating safe to reach for. A tour whose every step
131
- * is branch-specific (the parked-run tour: some boards have a decision waiting, some an
132
- * approval) would otherwise survive its own `when` and open on an empty cursor which the
183
+ * Three values rather than a boolean, because the two unavailable ones need different copy
184
+ * and different action from the reader: `blocked` names things they can go and do (link a
185
+ * repository, start a run), while `not-applicable` is a tour whose every step is about a
186
+ * branch this board isn't on — nothing to fix, and telling someone to fix it would send them
187
+ * looking for a control that was never missing.
188
+ *
189
+ * `blocked` OUTRANKS `not-applicable` when a tour is both, and that order is load-bearing: a
190
+ * step's `when` reads the same gates the requirements do, so under UNMET requirements the step
191
+ * filter is answering a hypothetical — what would apply on a board that, by construction, this
192
+ * one is not. Reporting that as `not-applicable` would tell the reader there is nothing to be
193
+ * done about a tour they can in fact unlock. The cost is the reverse case: a tour can be named
194
+ * as unlockable and still turn out `not-applicable` once the requirement is met. See
195
+ * {@link resolveTourCatalogue} for why a tour should not be authored that way.
196
+ */
197
+ export type TutorialAvailability = 'ready' | 'blocked' | 'not-applicable'
198
+
199
+ /** One tour as this board sees it: the resolved script plus why it can (or can't) run. */
200
+ export interface TutorialCatalogueEntry {
201
+ /** The tour with its inapplicable steps already dropped — what a start would run. */
202
+ tour: TutorialTour
203
+ availability: TutorialAvailability
204
+ /** The requirements this board/user does not meet. Empty unless `blocked`. */
205
+ unmet: readonly TutorialRequirement[]
206
+ }
207
+
208
+ /**
209
+ * Resolve the whole catalog against the gates: which tours can run now, and for the rest,
210
+ * exactly what is standing in the way.
211
+ *
212
+ * Per-step `when`s are applied here too, and a tour left with NO applicable steps is
213
+ * `not-applicable` rather than ready. That rule is what makes per-step gating safe to reach
214
+ * for: a tour whose every step is branch-specific (the parked-run tour — some boards have a
215
+ * decision waiting, some an approval) would otherwise open on an empty cursor, which the
133
216
  * overlay ends immediately, so the user presses Start and nothing happens.
134
217
  *
135
- * Pure and total, so `navSlotFilter` (which runs inside a reactive computed, once per gate
136
- * flip) stays a straight fold and the rules above are unit-testable without a Vue runtime.
218
+ * `gates` is nullable for the same reason `navSlotFilter` passes everything through when no
219
+ * gates service is wired (a bare install / dev-open parity): with nothing to gate against,
220
+ * nothing is withheld — including the per-step branches, which must not be silently thinned.
221
+ *
222
+ * AUTHORING RULE, and the reason `blocked` outranks `not-applicable` is safe in practice: give
223
+ * every tour at least one step with NO `when`. An intro and a finish card qualify, which is why
224
+ * every built-in has two (pinned by `tutorial-tours.spec.ts`). Then `steps` can never be empty,
225
+ * `not-applicable` is reachable only for a tour authored as branch-specific END TO END, and a
226
+ * tour named in the catalogue as unlockable can never turn out unstartable after the reader has
227
+ * gone and done the thing it asked for.
228
+ *
229
+ * Pure, total and sorted, so both consumers (the launch prompt's offer list and the
230
+ * catalogue) read one resolution rather than each re-deriving availability, and the rules
231
+ * above are unit-testable without a Vue runtime.
137
232
  */
138
- export function resolveTours(tours: readonly TutorialTour[], gates: NavGates): TutorialTour[] {
139
- const out: TutorialTour[] = []
140
- for (const tour of tours) {
141
- if (tour.when && !tour.when(gates)) continue
142
- const steps = tour.steps.filter((s) => (s.when ? s.when(gates) : true))
143
- if (steps.length === 0) continue
144
- // Reuse the original object when nothing was dropped: the prompt keys its list on the
145
- // tour, and a fresh object per gate read would re-render it on every unrelated flip.
146
- out.push(steps.length === tour.steps.length ? tour : { ...tour, steps })
233
+ export function resolveTourCatalogue(
234
+ tours: readonly TutorialTour[],
235
+ gates: NavGates | null,
236
+ ): TutorialCatalogueEntry[] {
237
+ return sortTours(tours).map((tour) => {
238
+ const unmet = gates ? (tour.requires ?? []).filter((r) => !r.met(gates)) : []
239
+ const steps = gates ? tour.steps.filter((s) => (s.when ? s.when(gates) : true)) : tour.steps
240
+ // Reuse the original object when nothing was dropped: both surfaces key their lists on
241
+ // the tour, and a fresh object per gate read would re-render on every unrelated flip.
242
+ const resolved = steps.length === tour.steps.length ? tour : { ...tour, steps }
243
+ const availability: TutorialAvailability =
244
+ unmet.length > 0 ? 'blocked' : steps.length === 0 ? 'not-applicable' : 'ready'
245
+ return { tour: resolved, availability, unmet }
246
+ })
247
+ }
248
+
249
+ /** The tours that can be started right now, resolved — the launch prompt's offer list. */
250
+ export function resolveTours(
251
+ tours: readonly TutorialTour[],
252
+ gates: NavGates | null,
253
+ ): TutorialTour[] {
254
+ return resolveTourCatalogue(tours, gates)
255
+ .filter((entry) => entry.availability === 'ready')
256
+ .map((entry) => entry.tour)
257
+ }
258
+
259
+ /**
260
+ * Where a tour stands for this user: the state the catalogue badges and the action label
261
+ * derive from. Camel-cased because the values ARE the i18n leaf keys
262
+ * (`tutorial.status.<state>`, `tutorial.action.<action>`), which keeps those lookups total.
263
+ */
264
+ export type TutorialTourState = 'notStarted' | 'inProgress' | 'paused' | 'completed'
265
+
266
+ /** What the tour's button does, given that state. */
267
+ export type TutorialLaunchAction = 'start' | 'resume' | 'restart' | 'continue'
268
+
269
+ /**
270
+ * Which state a tour is in, in precedence order: the one RUNNING wins over everything, then a
271
+ * broken-off position, then completion.
272
+ *
273
+ * Paused beating completed is the deliberate half: a tour taken again and broken off is
274
+ * offered where it stopped, rather than being described by the badge it earned last time.
275
+ */
276
+ export function tourState(input: {
277
+ active: boolean
278
+ resumable: boolean
279
+ completed: boolean
280
+ }): TutorialTourState {
281
+ if (input.active) return 'inProgress'
282
+ if (input.resumable) return 'paused'
283
+ return input.completed ? 'completed' : 'notStarted'
284
+ }
285
+
286
+ /**
287
+ * The action offered for a state. Total, so a new state cannot reach a surface without an
288
+ * action — `continue` exists because the catalogue is reachable DURING a tour (nothing about
289
+ * the overlay blocks the sidebar), and offering "Start" for the walkthrough already on screen
290
+ * would restart it from step one on a click most people would read as "back to it".
291
+ */
292
+ export function launchActionFor(state: TutorialTourState): TutorialLaunchAction {
293
+ const actions: Record<TutorialTourState, TutorialLaunchAction> = {
294
+ notStarted: 'start',
295
+ inProgress: 'continue',
296
+ paused: 'resume',
297
+ completed: 'restart',
147
298
  }
148
- return out
299
+ return actions[state]
300
+ }
301
+
302
+ /**
303
+ * The copy for each state / action, as exhaustive `Record`s rather than a key assembled at
304
+ * the call site (`t(\`tutorial.action.${action}\`)`).
305
+ *
306
+ * The typed-message-key check only covers keys written as literals, so an assembled one is
307
+ * exactly the drift it cannot see — the i18n guard's tier-2 rule. Declared this way, adding a
308
+ * state without its copy fails the typecheck, and `tutorial.spec.ts` pins every value against
309
+ * the catalog so a RENAMED key fails a test instead of rendering a raw path to the user.
310
+ */
311
+ export const TUTORIAL_STATUS_KEYS: Record<TutorialTourState, string> = {
312
+ notStarted: 'tutorial.status.notStarted',
313
+ inProgress: 'tutorial.status.inProgress',
314
+ paused: 'tutorial.status.paused',
315
+ completed: 'tutorial.status.completed',
316
+ }
317
+
318
+ export const TUTORIAL_ACTION_KEYS: Record<TutorialLaunchAction, string> = {
319
+ start: 'tutorial.action.start',
320
+ resume: 'tutorial.action.resume',
321
+ restart: 'tutorial.action.restart',
322
+ continue: 'tutorial.action.continue',
149
323
  }
150
324
 
151
325
  /** A DOMRect-shaped box, structurally typed so the geometry below is unit-testable. */
@@ -156,6 +330,43 @@ export interface TutorialRect {
156
330
  height: number
157
331
  }
158
332
 
333
+ /**
334
+ * How much of `rect` lies inside the viewport, in square pixels. Zero when they don't overlap
335
+ * at all, which is the case that matters: an anchor scrolled or panned off screen.
336
+ */
337
+ export function visibleArea(
338
+ rect: TutorialRect,
339
+ viewport: { width: number; height: number },
340
+ ): number {
341
+ const overlapWidth = Math.min(rect.left + rect.width, viewport.width) - Math.max(rect.left, 0)
342
+ const overlapHeight = Math.min(rect.top + rect.height, viewport.height) - Math.max(rect.top, 0)
343
+ return Math.max(0, overlapWidth) * Math.max(0, overlapHeight)
344
+ }
345
+
346
+ /**
347
+ * Does the overlay have to bring this anchor into view before the step can read correctly?
348
+ *
349
+ * The runtime accepts any element with layout boxes, and an element scrolled out of a panel or
350
+ * panned off the board canvas still HAS them — so without this check the highlight ring is
351
+ * drawn at off-screen coordinates while `computeCoachMarkLayout` clamps the tooltip to a
352
+ * viewport edge, leaving the user reading "click this" beside nothing at all. It bites the
353
+ * most-travelled steps hardest: the two `task-card` steps anchor whichever card is first in
354
+ * the DOM, which on a populated board is the one least likely to be the one on screen.
355
+ *
356
+ * Pure, so the threshold is pinned by unit tests rather than eyeballed against a real board.
357
+ * A zero-area anchor never needs revealing: there is no position to bring anywhere, and
358
+ * treating it as off-screen would make every degenerate rect trigger a canvas pan.
359
+ */
360
+ export function needsReveal(
361
+ rect: TutorialRect,
362
+ viewport: { width: number; height: number },
363
+ ): boolean {
364
+ const area = rect.width * rect.height
365
+ if (area <= 0) return false
366
+ const required = MIN_VISIBLE_RATIO * Math.min(area, viewport.width * viewport.height)
367
+ return visibleArea(rect, viewport) < required
368
+ }
369
+
159
370
  export interface CoachMarkLayout {
160
371
  top: number
161
372
  left: number
@@ -2162,7 +2162,6 @@
2162
2162
  "shortcuts": "Tastenkürzel",
2163
2163
  "bugHunt": "Fehlerjagd",
2164
2164
  "toggleUiMode": "Oberflächenmodus wechseln",
2165
- "tutorial": "Tour starten",
2166
2165
  "foundationalServices": "Basisdienste"
2167
2166
  },
2168
2167
  "keywords": {
@@ -3733,7 +3732,17 @@
3733
3732
  "consensusGroupsActive": "Dieser Schritt führt die ausgewählte Gruppe aus. Hebe die Auswahl aller Gruppen auf, um die Teilnehmer hier zu konfigurieren.",
3734
3733
  "consensusGroupAlways": "immer",
3735
3734
  "variantLabel": "Prompt-Variante",
3736
- "variantShipped": "Ausgelieferter Prompt"
3735
+ "variantShipped": "Ausgelieferter Prompt",
3736
+ "binaryOutputStorage": "Speichern über",
3737
+ "binaryOutputContext": "Kontext der Erzeugung",
3738
+ "binaryOutputPlaceholder": "Speicherdienst wählen",
3739
+ "binaryOutputContextPlaceholder": "Optional: Dienste, die den Umfang bestimmen",
3740
+ "binaryOutputNeedsPick": "Für einen Schritt, der binäre Ausgaben erzeugt, ist kein Speicherdienst gewählt. Wähle einen vor dem Speichern.",
3741
+ "binaryOutputNoStorage": "Kein Dienst im Katalog dieses Boards deklariert die Fähigkeit {capability}. Registriere einen, oder ergänze die Fähigkeit beim gemeinten Dienst unter den grundlegenden Diensten.",
3742
+ "binaryOutputMissing": "Dieser Speicherdienst ist nicht mehr im Katalog; wähle einen anderen.",
3743
+ "binaryOutputNotStorage": "Dieser Dienst deklariert die Fähigkeit {capability} nicht mehr, deshalb werden Läufe abgelehnt; wähle einen anderen.",
3744
+ "binaryOutputContextMissing": "Diese Kontextdienste sind nicht mehr im Katalog: {ids}",
3745
+ "binaryOutputUnavailable": "Der Katalog der grundlegenden Dienste ist nicht erreichbar, deshalb lässt sich hier noch nichts wählen."
3737
3746
  },
3738
3747
  "progress": {
3739
3748
  "status": {
@@ -4384,6 +4393,47 @@
4384
4393
  "unlinkSourceFailed": "Repo-Verknüpfung konnte nicht gelöst werden"
4385
4394
  }
4386
4395
  },
4396
+ "binaryOutput": {
4397
+ "heading": "Binäre Ausgaben",
4398
+ "target": "Speicherdienst",
4399
+ "targetNone": "Für diesen Schritt nicht erfasst",
4400
+ "contextServices": "Kontext der Erzeugung",
4401
+ "misdirectedBadge": "Anderer Dienst",
4402
+ "unknownBadge": "Nicht im Katalog",
4403
+ "storedCount": "{outcome}, 1 Artefakt | {outcome}, {count} Artefakte",
4404
+ "state": {
4405
+ "notStarted": {
4406
+ "summary": "Noch nicht gestartet",
4407
+ "detail": "Dieser Schritt hat noch nicht begonnen. Wenn er läuft, speichert er seine binären Ausgaben über den unten genannten Dienst."
4408
+ },
4409
+ "configured": {
4410
+ "summary": "Noch nichts erfasst",
4411
+ "detail": "Dieser Schritt ist auf das Speichern binärer Ausgaben eingerichtet, hat aber noch keine gemeldet. Er läuft noch, oder er endete vor seiner Meldung."
4412
+ },
4413
+ "undeclared": {
4414
+ "summary": "Nichts angegeben",
4415
+ "detail": "Dieser Schritt endete, ohne anzugeben, was er gespeichert hat. Möglicherweise hat er etwas gespeichert, möglicherweise nicht; erfasst wurde in beiden Fällen nichts."
4416
+ },
4417
+ "parseFailed": {
4418
+ "summary": "Angabe unlesbar",
4419
+ "detail": "Dieser Schritt endete mit einer Angabe, welche die Plattform nicht lesen konnte, deshalb wurde nichts erfasst. Was er gespeichert hat, fehlt in der Liste unten."
4420
+ },
4421
+ "declaredNone": {
4422
+ "summary": "Nichts gespeichert",
4423
+ "detail": "Der Agent hat gemeldet, dass er keine binären Artefakte gespeichert hat. Das ist ein gültiges Ergebnis, kein Fehler."
4424
+ },
4425
+ "stored": {
4426
+ "summary": "Gespeichert"
4427
+ }
4428
+ },
4429
+ "warning": {
4430
+ "unknownServices": "Nennt einen Dienst, den der Katalog nicht enthält: {ids}. Der Eintrag bleibt wie angegeben erhalten; prüfe die Kennung gegen den Katalog des Boards. | Nennt Dienste, die der Katalog nicht enthält: {ids}. Die Einträge bleiben wie angegeben erhalten; prüfe die Kennungen gegen den Katalog des Boards.",
4431
+ "targetUnknown": "Der Katalog enthält den eigenen Speicherdienst dieses Schritts nicht mehr ({id}), deshalb konnte nichts unten dagegen geprüft werden. Registriere ihn erneut, oder verweise den Schritt auf einen anderen Dienst.",
4432
+ "misdirected": "1 Artefakt ging an einen anderen Dienst als {target}. | {count} Artefakte gingen an einen anderen Dienst als {target}.",
4433
+ "invalidEntries": "1 angegebener Eintrag wurde verworfen: er nannte weder Dienst noch Ablageort. | {count} angegebene Einträge wurden verworfen: sie nannten weder Dienst noch Ablageort.",
4434
+ "omitted": "1 weiteres Artefakt wurde jenseits der Berichtsgrenze angegeben und ist nicht aufgeführt. | {count} weitere Artefakte wurden jenseits der Berichtsgrenze angegeben und sind nicht aufgeführt."
4435
+ }
4436
+ },
4387
4437
  "brainstorm": {
4388
4438
  "title": {
4389
4439
  "architecture": "Architektur-Brainstorm",
@@ -4823,7 +4873,9 @@
4823
4873
  "accountSettings": "Kontoeinstellungen",
4824
4874
  "operatorDashboard": "Plattform-Observability",
4825
4875
  "reports": "Berichte",
4826
- "foundationalServices": "Basisdienste"
4876
+ "foundationalServices": "Basisdienste",
4877
+ "tutorials": "Tutorials",
4878
+ "help": "Hilfe"
4827
4879
  },
4828
4880
  "errors": {
4829
4881
  "generic": {
@@ -6125,12 +6177,41 @@
6125
6177
  "prompt": {
6126
6178
  "title": "Eine kurze Tour machen?",
6127
6179
  "intro": "Geführte Touren zeigen dir die App direkt auf diesem Bildschirm: Sie heben die echten Bedienelemente hervor und sagen dir, was du anklicken sollst.",
6128
- "start": "Starten",
6129
- "restart": "Wiederholen",
6130
- "completed": "Abgeschlossen",
6131
6180
  "decline": "Nein danke",
6132
6181
  "later": "Vielleicht später",
6133
- "empty": "Für deine Rolle auf diesem Board sind noch keine Touren verfügbar."
6182
+ "empty": "Für deine Rolle auf diesem Board sind noch keine Touren verfügbar.",
6183
+ "browse": "Alle Tutorials anzeigen"
6184
+ },
6185
+ "catalogue": {
6186
+ "title": "Tutorials",
6187
+ "intro": "Geführte Rundgänge, die dir unterwegs die echten Bedienelemente hervorheben. Du kannst jeden davon jederzeit starten und beliebig oft wiederholen.",
6188
+ "progress": "{completed} von {total} abgeschlossen",
6189
+ "steps": "1 Schritt | {count} Schritte",
6190
+ "blocked": "Verfügbar, sobald du Folgendes hast:",
6191
+ "notApplicable": "Auf diesem Board gibt es derzeit nichts, was dieser Rundgang zeigen könnte.",
6192
+ "empty": "Diese Installation enthält keine Tutorials.",
6193
+ "reset": "Fortschritt zurücksetzen",
6194
+ "resetHint": "Vergisst, welche Tutorials du abgeschlossen hast, und fragt dich beim nächsten Besuch erneut nach der Tour."
6195
+ },
6196
+ "action": {
6197
+ "start": "Starten",
6198
+ "resume": "Fortsetzen",
6199
+ "restart": "Wiederholen",
6200
+ "continue": "Zurück zur Tour"
6201
+ },
6202
+ "status": {
6203
+ "notStarted": "Nicht begonnen",
6204
+ "inProgress": "Läuft",
6205
+ "paused": "Pausiert",
6206
+ "completed": "Abgeschlossen"
6207
+ },
6208
+ "requirements": {
6209
+ "boardWrite": "Berechtigung, dieses Board zu bearbeiten",
6210
+ "sourceControl": "Eine verbundene Quellcodeverwaltung",
6211
+ "service": "Ein Service auf dem Board",
6212
+ "task": "Eine Aufgabe auf dem Board",
6213
+ "waitingAnswer": "Ein Lauf, der auf deine Antwort wartet",
6214
+ "finishedRun": "Ein erfolgreich abgeschlossener Lauf"
6134
6215
  },
6135
6216
  "overlay": {
6136
6217
  "next": "Weiter",
@@ -6139,6 +6220,7 @@
6139
6220
  "done": "Fertig",
6140
6221
  "progress": "Schritt {current} von {total}",
6141
6222
  "ariaLabel": "Tutorial-Schritt",
6223
+ "announcement": "Schritt {current} von {total}. {title}. {body}",
6142
6224
  "abridged": "1 Schritt wurde übersprungen: dieses Element gehört nicht zu diesem Board. | {count} Schritte wurden übersprungen: diese Elemente gehören nicht zu diesem Board.",
6143
6225
  "searching": "Suche das hervorgehobene Element...",
6144
6226
  "clickHint": "Klicke auf das hervorgehobene Element, um fortzufahren"
@@ -151,7 +151,9 @@
151
151
  "accountSettings": "Account settings",
152
152
  "operatorDashboard": "Platform observability",
153
153
  "reports": "Reports",
154
- "foundationalServices": "Foundational services"
154
+ "foundationalServices": "Foundational services",
155
+ "tutorials": "Tutorials",
156
+ "help": "Help"
155
157
  },
156
158
  "board": {
157
159
  "toast": {
@@ -2164,7 +2166,6 @@
2164
2166
  "shortcuts": "Keyboard shortcuts",
2165
2167
  "bugHunt": "Bug hunt",
2166
2168
  "toggleUiMode": "Switch interface mode",
2167
- "tutorial": "Take a tour",
2168
2169
  "foundationalServices": "Foundational services"
2169
2170
  },
2170
2171
  "keywords": {
@@ -4208,7 +4209,17 @@
4208
4209
  "consensusGroupsActive": "This step runs the selected group. Deselect every group to configure participants here instead.",
4209
4210
  "consensusGroupAlways": "always",
4210
4211
  "variantLabel": "Prompt variant",
4211
- "variantShipped": "Shipped prompt"
4212
+ "variantShipped": "Shipped prompt",
4213
+ "binaryOutputStorage": "Store through",
4214
+ "binaryOutputContext": "Generation context",
4215
+ "binaryOutputPlaceholder": "Pick a storage service",
4216
+ "binaryOutputContextPlaceholder": "Optional: services that scope the generation",
4217
+ "binaryOutputNeedsPick": "A step that generates binary outputs has no storage service selected. Pick one before saving.",
4218
+ "binaryOutputNoStorage": "No service in this board's catalog declares the {capability} capability. Register one, or add the capability to the service you meant, under foundational services.",
4219
+ "binaryOutputMissing": "This storage service is no longer in the catalog; pick another.",
4220
+ "binaryOutputNotStorage": "This service no longer declares the {capability} capability, so runs will be refused; pick another.",
4221
+ "binaryOutputContextMissing": "These context services are no longer in the catalog: {ids}",
4222
+ "binaryOutputUnavailable": "The foundational services catalog is unreachable, so nothing can be picked here yet."
4212
4223
  },
4213
4224
  "progress": {
4214
4225
  "status": {
@@ -5592,6 +5603,47 @@
5592
5603
  "unlinkSourceFailed": "Could not unlink the repo"
5593
5604
  }
5594
5605
  },
5606
+ "binaryOutput": {
5607
+ "heading": "Binary outputs",
5608
+ "target": "Storage service",
5609
+ "targetNone": "None recorded on this step",
5610
+ "contextServices": "Generation context",
5611
+ "misdirectedBadge": "Other service",
5612
+ "unknownBadge": "Not in catalog",
5613
+ "storedCount": "{outcome}, 1 artifact | {outcome}, {count} artifacts",
5614
+ "state": {
5615
+ "notStarted": {
5616
+ "summary": "Not started yet",
5617
+ "detail": "This step has not started yet. When it runs, it will store its binary outputs through the service below."
5618
+ },
5619
+ "configured": {
5620
+ "summary": "Nothing recorded yet",
5621
+ "detail": "This step is set up to store binary outputs, but it has not reported any yet. It is still running, or it ended before it reported."
5622
+ },
5623
+ "undeclared": {
5624
+ "summary": "Nothing declared",
5625
+ "detail": "This step finished without declaring what it stored. It may or may not have stored something; either way, nothing was recorded."
5626
+ },
5627
+ "parseFailed": {
5628
+ "summary": "Declaration unreadable",
5629
+ "detail": "This step ended with a declaration the platform could not read, so nothing was recorded. Anything it stored is missing from the list below."
5630
+ },
5631
+ "declaredNone": {
5632
+ "summary": "Stored nothing",
5633
+ "detail": "The agent reported that it stored no binary artifacts. That is a valid outcome, not a failure."
5634
+ },
5635
+ "stored": {
5636
+ "summary": "Stored"
5637
+ }
5638
+ },
5639
+ "warning": {
5640
+ "unknownServices": "Named a service the catalog does not contain: {ids}. The entry is kept as claimed; check the id against the workspace catalog. | Named services the catalog does not contain: {ids}. Their entries are kept as claimed; check the ids against the workspace catalog.",
5641
+ "targetUnknown": "The catalog no longer contains this step's own storage service ({id}), so nothing below could be checked against it. Register it again, or point the step at another service.",
5642
+ "misdirected": "1 artifact went to a service other than {target}. | {count} artifacts went to a service other than {target}.",
5643
+ "invalidEntries": "1 declared entry was dropped: it named no service and location. | {count} declared entries were dropped: they named no service and location.",
5644
+ "omitted": "1 more artifact was declared beyond the report's limit and is not listed. | {count} more artifacts were declared beyond the report's limit and are not listed."
5645
+ }
5646
+ },
5595
5647
  "sandbox": {
5596
5648
  "title": "Sandbox: prompt and model testing",
5597
5649
  "description": "Try prompt versions and models against graded fixtures, scored by a judge model.",
@@ -6329,12 +6381,50 @@
6329
6381
  "prompt": {
6330
6382
  "title": "Take a quick tour?",
6331
6383
  "intro": "Guided tours walk you through the app right on this screen: they highlight the actual controls and tell you what to click.",
6332
- "start": "Start",
6333
- "restart": "Repeat",
6334
- "completed": "Completed",
6335
6384
  "decline": "No thanks",
6336
6385
  "later": "Maybe later",
6337
- "empty": "No tours are available for your role on this board yet."
6386
+ "empty": "No tours are available for your role on this board yet.",
6387
+ "browse": "See all tutorials"
6388
+ },
6389
+ "catalogue": {
6390
+ "title": "Tutorials",
6391
+ "intro": "Guided walkthroughs that highlight the real controls as you go. Start any of them whenever you like, and take one again as often as you want.",
6392
+ "progress": "{completed} of {total} completed",
6393
+ "steps": "1 step | {count} steps",
6394
+ "@steps": {
6395
+ "description": "The length of one walkthrough. Needs the plural forms of the target language (pl/uk take three: one | few | many); {count} is the number of steps."
6396
+ },
6397
+ "blocked": "Available once you have:",
6398
+ "notApplicable": "Nothing on this board matches this walkthrough right now.",
6399
+ "empty": "This deployment ships no tutorials.",
6400
+ "reset": "Reset progress",
6401
+ "resetHint": "Forget which tutorials you have finished and ask about the tour again on your next visit."
6402
+ },
6403
+ "action": {
6404
+ "start": "Start",
6405
+ "resume": "Resume",
6406
+ "@resume": {
6407
+ "description": "Verb, on a button: pick a guided tour back up from where it was broken off. Not the noun (CV/resume)."
6408
+ },
6409
+ "restart": "Repeat",
6410
+ "continue": "Back to the tour",
6411
+ "@continue": {
6412
+ "description": "Button on the tour that is already running: return to it. It does not restart the walkthrough from the beginning."
6413
+ }
6414
+ },
6415
+ "status": {
6416
+ "notStarted": "Not started",
6417
+ "inProgress": "In progress",
6418
+ "paused": "Paused",
6419
+ "completed": "Completed"
6420
+ },
6421
+ "requirements": {
6422
+ "boardWrite": "Permission to edit this board",
6423
+ "sourceControl": "A connected source-control provider",
6424
+ "service": "A service on the board",
6425
+ "task": "A task on the board",
6426
+ "waitingAnswer": "A run waiting for your answer",
6427
+ "finishedRun": "A run that finished successfully"
6338
6428
  },
6339
6429
  "overlay": {
6340
6430
  "next": "Next",
@@ -6343,6 +6433,10 @@
6343
6433
  "done": "Finish",
6344
6434
  "progress": "Step {current} of {total}",
6345
6435
  "ariaLabel": "Tutorial step",
6436
+ "announcement": "Step {current} of {total}. {title}. {body}",
6437
+ "@announcement": {
6438
+ "description": "Screen-reader-only announcement read out on every step change; it is never shown on screen. {title} and {body} are the step's own heading and full sentence, so the separators around them have to be real sentence punctuation in the target language."
6439
+ },
6346
6440
  "abridged": "1 step was skipped: that control is not part of this board. | {count} steps were skipped: those controls are not part of this board.",
6347
6441
  "@abridged": {
6348
6442
  "description": "Shown on a tour's final card when steps were skipped. Needs the plural forms of the target language (pl/uk take three: one | few | many); {count} is the number of skipped steps."