@cat-factory/app 0.259.3 → 0.260.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.
@@ -0,0 +1,236 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { createSettlingLoop, type FrameScheduler } from './settlingLoop'
3
+
4
+ /** A hand-driven frame clock: `flush()` runs exactly one scheduled frame. */
5
+ function fakeScheduler() {
6
+ let nextHandle = 1
7
+ const pending = new Map<number, () => void>()
8
+ const scheduler: FrameScheduler = {
9
+ schedule(run) {
10
+ const handle = nextHandle++
11
+ pending.set(handle, run)
12
+ return handle
13
+ },
14
+ cancel(handle) {
15
+ pending.delete(handle)
16
+ },
17
+ }
18
+ return {
19
+ scheduler,
20
+ pending: () => pending.size,
21
+ /** Run every currently scheduled frame; frames they schedule wait for the next flush. */
22
+ flush() {
23
+ const due = [...pending.entries()]
24
+ pending.clear()
25
+ for (const [, run] of due) run()
26
+ },
27
+ }
28
+ }
29
+
30
+ describe('createSettlingLoop', () => {
31
+ it('does not run until poked', () => {
32
+ const clock = fakeScheduler()
33
+ let frames = 0
34
+ const loop = createSettlingLoop({
35
+ compute: () => {
36
+ frames++
37
+ return false
38
+ },
39
+ scheduler: clock.scheduler,
40
+ settleFrames: 3,
41
+ })
42
+
43
+ expect(loop.awake()).toBe(false)
44
+ clock.flush()
45
+ expect(frames).toBe(0)
46
+ })
47
+
48
+ it('keeps running while the output changes, and parks once it holds still', () => {
49
+ const clock = fakeScheduler()
50
+ let changed = true
51
+ let frames = 0
52
+ const loop = createSettlingLoop({
53
+ compute: () => {
54
+ frames++
55
+ return changed
56
+ },
57
+ scheduler: clock.scheduler,
58
+ settleFrames: 3,
59
+ })
60
+
61
+ loop.poke()
62
+ for (let i = 0; i < 10; i++) clock.flush()
63
+ expect(frames).toBe(10)
64
+ expect(loop.awake()).toBe(true)
65
+
66
+ // The animation ends: three unchanged frames later the loop is parked and the frame
67
+ // count stops moving no matter how many times the clock ticks.
68
+ changed = false
69
+ clock.flush()
70
+ clock.flush()
71
+ expect(loop.awake()).toBe(true)
72
+ clock.flush()
73
+ expect(loop.awake()).toBe(false)
74
+
75
+ const settledAt = frames
76
+ for (let i = 0; i < 10; i++) clock.flush()
77
+ expect(frames).toBe(settledAt)
78
+ })
79
+
80
+ it('runs the settle tail after a poke that changed nothing, then parks', () => {
81
+ const clock = fakeScheduler()
82
+ let frames = 0
83
+ const loop = createSettlingLoop({
84
+ compute: () => {
85
+ frames++
86
+ return false
87
+ },
88
+ scheduler: clock.scheduler,
89
+ settleFrames: 3,
90
+ })
91
+
92
+ // A signal fires one frame BEFORE the transition it starts produces geometry, so a wake
93
+ // that measures no change still owes the tail rather than parking immediately.
94
+ loop.poke()
95
+ clock.flush()
96
+ expect(loop.awake()).toBe(true)
97
+ clock.flush()
98
+ clock.flush()
99
+ expect(frames).toBe(3)
100
+ expect(loop.awake()).toBe(false)
101
+ })
102
+
103
+ it('wakes a parked loop again on the next poke', () => {
104
+ const clock = fakeScheduler()
105
+ let frames = 0
106
+ const loop = createSettlingLoop({
107
+ compute: () => {
108
+ frames++
109
+ return false
110
+ },
111
+ scheduler: clock.scheduler,
112
+ settleFrames: 1,
113
+ })
114
+
115
+ loop.poke()
116
+ clock.flush()
117
+ expect(loop.awake()).toBe(false)
118
+
119
+ loop.poke()
120
+ clock.flush()
121
+ expect(frames).toBe(2)
122
+ })
123
+
124
+ it('restarts the countdown on a poke without scheduling a second frame', () => {
125
+ const clock = fakeScheduler()
126
+ let frames = 0
127
+ const loop = createSettlingLoop({
128
+ compute: () => {
129
+ frames++
130
+ return false
131
+ },
132
+ scheduler: clock.scheduler,
133
+ settleFrames: 2,
134
+ })
135
+
136
+ loop.poke()
137
+ loop.poke()
138
+ loop.poke()
139
+ expect(clock.pending()).toBe(1)
140
+ clock.flush()
141
+ expect(frames).toBe(1)
142
+ })
143
+
144
+ it('does not schedule a second frame when the compute itself pokes', () => {
145
+ const clock = fakeScheduler()
146
+ let frames = 0
147
+ // A compute that writes to a store can wake watchers that poke back synchronously. That
148
+ // must reset the countdown, not double the frame rate.
149
+ const loop = createSettlingLoop({
150
+ compute: () => {
151
+ frames++
152
+ loop.poke()
153
+ return false
154
+ },
155
+ scheduler: clock.scheduler,
156
+ settleFrames: 2,
157
+ })
158
+
159
+ loop.poke()
160
+ for (let i = 0; i < 5; i++) {
161
+ expect(clock.pending()).toBe(1)
162
+ clock.flush()
163
+ }
164
+ expect(frames).toBe(5)
165
+ })
166
+
167
+ it('parks a throwing compute so a later poke still wakes it', () => {
168
+ const clock = fakeScheduler()
169
+ let frames = 0
170
+ let broken = true
171
+ const loop = createSettlingLoop({
172
+ compute: () => {
173
+ frames++
174
+ if (broken) throw new Error('measured a card that just unmounted')
175
+ return false
176
+ },
177
+ scheduler: clock.scheduler,
178
+ settleFrames: 3,
179
+ })
180
+
181
+ // The frame the throw escaped from is already spent. Staying awake would leave the loop
182
+ // holding a stream it can never schedule on again, and every later poke a no-op.
183
+ loop.poke()
184
+ expect(() => clock.flush()).toThrow('measured a card that just unmounted')
185
+ expect(loop.awake()).toBe(false)
186
+ expect(clock.pending()).toBe(0)
187
+
188
+ broken = false
189
+ loop.poke()
190
+ clock.flush()
191
+ expect(frames).toBe(2)
192
+ expect(loop.awake()).toBe(true)
193
+ })
194
+
195
+ it('stays parked when the compute stops the loop', () => {
196
+ const clock = fakeScheduler()
197
+ let frames = 0
198
+ // A compute whose store write unmounts the board runs `stop()` from inside the frame it
199
+ // is halfway through. The frame it was about to schedule must not resurrect it.
200
+ const loop = createSettlingLoop({
201
+ compute: () => {
202
+ frames++
203
+ loop.stop()
204
+ return true
205
+ },
206
+ scheduler: clock.scheduler,
207
+ settleFrames: 3,
208
+ })
209
+
210
+ loop.poke()
211
+ clock.flush()
212
+ expect(loop.awake()).toBe(false)
213
+ expect(clock.pending()).toBe(0)
214
+ clock.flush()
215
+ expect(frames).toBe(1)
216
+ })
217
+
218
+ it('drops the pending frame on stop', () => {
219
+ const clock = fakeScheduler()
220
+ let frames = 0
221
+ const loop = createSettlingLoop({
222
+ compute: () => {
223
+ frames++
224
+ return true
225
+ },
226
+ scheduler: clock.scheduler,
227
+ settleFrames: 3,
228
+ })
229
+
230
+ loop.poke()
231
+ loop.stop()
232
+ expect(loop.awake()).toBe(false)
233
+ clock.flush()
234
+ expect(frames).toBe(0)
235
+ })
236
+ })
@@ -0,0 +1,101 @@
1
+ /**
2
+ * A frame loop that stops itself once its output stops changing.
3
+ *
4
+ * The board's DOM-measuring drivers (dependency edges, task expansion) have to follow
5
+ * animations they cannot observe directly: a CSS height transition, a Vue Flow pan, a card
6
+ * reflowing after its text changed. Running them unconditionally every frame makes an idle
7
+ * board pay O(edges) forced layout reads 60 times a second; running them only on a change
8
+ * signal makes them stop mid-transition, because the signal fires when the transition
9
+ * STARTS and says nothing about the frames that follow.
10
+ *
11
+ * This resolves both: an external signal `poke()`s the loop awake, and the loop keeps
12
+ * running while `compute()` reports it changed something. Once the output has held still
13
+ * for `settleFrames` frames the animation is over and the loop parks at zero cost until the
14
+ * next poke.
15
+ *
16
+ * The scheduler is injected so the behaviour is testable without a browser frame clock.
17
+ */
18
+
19
+ /** `requestAnimationFrame` / `cancelAnimationFrame`, injected so tests can drive frames by hand. */
20
+ export type FrameScheduler = {
21
+ schedule: (run: () => void) => number
22
+ cancel: (handle: number) => void
23
+ }
24
+
25
+ export type SettlingLoop = {
26
+ /** Wake the loop, and reset the settle countdown if it is already awake. */
27
+ poke: () => void
28
+ /** Park the loop and drop the pending frame. Idempotent. */
29
+ stop: () => void
30
+ /**
31
+ * Whether the loop still owns the frame stream: a frame is scheduled, or `compute()` is
32
+ * running right now. The two are deliberately not the same fact, and only this one decides
33
+ * whether a `poke()` has to schedule anything.
34
+ */
35
+ awake: () => boolean
36
+ }
37
+
38
+ /**
39
+ * How many unchanged frames end a run. A signal fires when a style or class changes, one
40
+ * frame BEFORE the transition it starts produces any geometry, so parking on the first
41
+ * unchanged frame would miss every animation. Four frames (~66ms at 60Hz) clears that gap
42
+ * while keeping a false wake-up cheap.
43
+ */
44
+ export const DEFAULT_SETTLE_FRAMES = 4
45
+
46
+ export function createSettlingLoop(options: {
47
+ /** Runs one frame; returns whether it changed anything the user can see. */
48
+ compute: () => boolean
49
+ scheduler: FrameScheduler
50
+ settleFrames?: number
51
+ }): SettlingLoop {
52
+ const { compute, scheduler } = options
53
+ const settleFrames = options.settleFrames ?? DEFAULT_SETTLE_FRAMES
54
+ /** The scheduled frame's handle, and ONLY that: null the whole time `compute()` runs. */
55
+ let pending: number | null = null
56
+ /** Whether the loop owns the frame stream, which stays true across `compute()`. */
57
+ let isAwake = false
58
+ let unchangedFrames = 0
59
+
60
+ function frame() {
61
+ // This callback's own handle is spent the moment it runs, so nothing may cancel it later;
62
+ // `isAwake` is what carries "the loop is running" across the compute below.
63
+ pending = null
64
+ let changed: boolean
65
+ try {
66
+ changed = compute()
67
+ } catch (error) {
68
+ // Park before letting the error reach the frame callback, where the browser reports it.
69
+ // Staying awake with no frame scheduled would make every later `poke()` a no-op, so one
70
+ // throwing frame would freeze the board for the rest of the session; rescheduling would
71
+ // be worse still, since a compute that threw on this frame throws on the next one too
72
+ // and a 60Hz error storm costs more than an arrow that waits for the next pulse.
73
+ isAwake = false
74
+ throw error
75
+ }
76
+ // A `stop()` that ran during `compute()` (an unmount driven by the compute's own store
77
+ // write) parks the loop for good: it must not be resurrected by the frame below.
78
+ if (!isAwake) return
79
+ unchangedFrames = changed ? 0 : unchangedFrames + 1
80
+ if (unchangedFrames < settleFrames) pending = scheduler.schedule(frame)
81
+ else isAwake = false
82
+ }
83
+
84
+ return {
85
+ poke() {
86
+ unchangedFrames = 0
87
+ // A poke triggered by the compute's own store write (a watcher, a re-render) resets the
88
+ // countdown and nothing more: `isAwake` is still set, so it cannot schedule a second
89
+ // frame beside the one `frame()` is about to schedule itself.
90
+ if (isAwake) return
91
+ isAwake = true
92
+ pending = scheduler.schedule(frame)
93
+ },
94
+ stop() {
95
+ isAwake = false
96
+ if (pending !== null) scheduler.cancel(pending)
97
+ pending = null
98
+ },
99
+ awake: () => isAwake,
100
+ }
101
+ }
@@ -3403,6 +3403,9 @@
3403
3403
  "monorepoBrowseHint": "Durchsuchen Sie das Repository und wählen Sie die Verzeichnisse der Services aus, die Sie hinzufügen möchten – aus jedem beliebigen Ordner. Agents, die an einem Service arbeiten, laufen innerhalb seines Unterverzeichnisses.",
3404
3404
  "selectedServices": "Ausgewählte Services",
3405
3405
  "noServicesSelected": "Noch keine Services ausgewählt. Wählen Sie oben Verzeichnisse aus.",
3406
+ "frontendLabel": "Frontend-App (optional)",
3407
+ "frontendHint": "Markieren Sie eines der ausgewählten Verzeichnisse als Frontend für die übrigen. Es wird als Frontend-App angelegt, auf dieses Unterverzeichnis festgelegt und mit jedem daneben hinzugefügten Backend-Service verknüpft. Die Umgebungsvariablen für die Backend-URLs benennen Sie anschließend im Inspector des Frontends.",
3408
+ "frontendNone": "Keines: alle Auswahlen sind Backend-Services",
3406
3409
  "addServices": "{count} Service hinzufügen | {count} Services hinzufügen",
3407
3410
  "removeService": "{directory} entfernen",
3408
3411
  "addedConfigure": "{title} hinzugefügt, konfigurieren Sie es",
@@ -3416,7 +3419,9 @@
3416
3419
  "addedDescription": "{title} ist auf dem Board, konfigurieren Sie es unten.",
3417
3420
  "addFailedTitle": "Service konnte nicht hinzugefügt werden",
3418
3421
  "servicesAddedTitle": "Services hinzugefügt",
3419
- "servicesAddedDescription": "{count} Service zum Board hinzugefügt. | {count} Services zum Board hinzugefügt."
3422
+ "servicesAddedDescription": "{count} Service zum Board hinzugefügt. | {count} Services zum Board hinzugefügt.",
3423
+ "frontendLinkedNote": "{directory} wurde als Frontend hinzugefügt und mit den übrigen verknüpft. Benennen Sie die Umgebungsvariablen für die Backend-URLs im Inspector.",
3424
+ "frontendWiringFailedNote": "Die Frontend-Einstellungen wurden nicht gespeichert. Öffnen Sie den Inspector jeder Frontend-App, um ihr Unterverzeichnis und ihre Backend-Services zu setzen."
3420
3425
  }
3421
3426
  },
3422
3427
  "repoTree": {
@@ -4271,6 +4271,9 @@
4271
4271
  "monorepoBrowseHint": "Browse the repository and select the directories of the services you want to add — from any folder. Agents working on a service run within its subdirectory.",
4272
4272
  "selectedServices": "Selected services",
4273
4273
  "noServicesSelected": "No services selected yet. Pick directories above.",
4274
+ "frontendLabel": "Frontend app (optional)",
4275
+ "frontendHint": "Mark one of the selected directories as the frontend for the rest. It is added as a frontend app pinned to that subdirectory and linked to every backend service added beside it. Name each backend URL environment variable afterwards in the frontend's inspector.",
4276
+ "frontendNone": "None: every selection is a backend service",
4274
4277
  "addServices": "Add {count} service | Add {count} services",
4275
4278
  "@addServices": {
4276
4279
  "description": "Count-driven button label; resolved via t(key, { count }, count) so {count} also drives the plural choice. 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)."
@@ -4290,7 +4293,9 @@
4290
4293
  "servicesAddedDescription": "{count} service added to the board. | {count} services added to the board.",
4291
4294
  "@servicesAddedDescription": {
4292
4295
  "description": "Count-driven toast; resolved via t(key, { count }, count) so {count} also drives the plural choice. Provide ALL plural forms your language needs (Polish/Ukrainian need 3 - one/few/many)."
4293
- }
4296
+ },
4297
+ "frontendLinkedNote": "{directory} was added as the frontend and linked to the others. Name its backend URL environment variables in its inspector.",
4298
+ "frontendWiringFailedNote": "The frontend settings did not save. Open each frontend app's inspector to set its subdirectory and its backend services."
4294
4299
  }
4295
4300
  },
4296
4301
  "repoTree": {
@@ -4138,6 +4138,9 @@
4138
4138
  "monorepoBrowseHint": "Explora el repositorio y selecciona los directorios de los servicios que quieres añadir, de cualquier carpeta. Los agentes que trabajen en un servicio se ejecutarán dentro de su subdirectorio.",
4139
4139
  "selectedServices": "Servicios seleccionados",
4140
4140
  "noServicesSelected": "Aún no hay servicios seleccionados. Elige directorios arriba.",
4141
+ "frontendLabel": "Aplicación frontend (opcional)",
4142
+ "frontendHint": "Marca uno de los directorios seleccionados como el frontend de los demás. Se añade como aplicación frontend anclada a ese subdirectorio y se enlaza con cada servicio backend añadido junto a él. Después, asigna en el inspector del frontend el nombre de cada variable de entorno con la URL del backend.",
4143
+ "frontendNone": "Ninguno: todas las selecciones son servicios backend",
4141
4144
  "addServices": "Añadir {count} servicio | Añadir {count} servicios",
4142
4145
  "removeService": "Quitar {directory}",
4143
4146
  "addedConfigure": "{title} añadido, configúralo",
@@ -4151,7 +4154,9 @@
4151
4154
  "addedDescription": "{title} está en el tablero, configúralo abajo.",
4152
4155
  "addFailedTitle": "No se pudo añadir el servicio",
4153
4156
  "servicesAddedTitle": "Servicios añadidos",
4154
- "servicesAddedDescription": "{count} servicio añadido al tablero. | {count} servicios añadidos al tablero."
4157
+ "servicesAddedDescription": "{count} servicio añadido al tablero. | {count} servicios añadidos al tablero.",
4158
+ "frontendLinkedNote": "{directory} se añadió como frontend y se enlazó con los demás. Asigna el nombre de sus variables de entorno con las URL de backend en su inspector.",
4159
+ "frontendWiringFailedNote": "Los ajustes del frontend no se guardaron. Abre el inspector de cada aplicación frontend para definir su subdirectorio y sus servicios backend."
4155
4160
  },
4156
4161
  "repoType": "Tipo de repositorio",
4157
4162
  "repoTypeHint": "Qué es este repositorio: un servicio backend, una aplicación frontend, una biblioteca compartida o un repositorio de documentación (solo documentos/spikes)."
@@ -4138,6 +4138,9 @@
4138
4138
  "monorepoBrowseHint": "Parcourez le dépôt et sélectionnez les répertoires des services que vous voulez ajouter, depuis n'importe quel dossier. Les agents travaillant sur un service s'exécutent dans son sous-répertoire.",
4139
4139
  "selectedServices": "Services sélectionnés",
4140
4140
  "noServicesSelected": "Aucun service sélectionné pour le moment. Choisissez des répertoires ci-dessus.",
4141
+ "frontendLabel": "Application frontend (facultatif)",
4142
+ "frontendHint": "Désignez l'un des répertoires sélectionnés comme le frontend des autres. Il est ajouté en tant qu'application frontend rattachée à ce sous-répertoire et relié à chaque service backend ajouté à ses côtés. Nommez ensuite chaque variable d'environnement d'URL backend dans l'inspecteur du frontend.",
4143
+ "frontendNone": "Aucun : toutes les sélections sont des services backend",
4141
4144
  "addServices": "Ajouter {count} service | Ajouter {count} services",
4142
4145
  "removeService": "Retirer {directory}",
4143
4146
  "addedConfigure": "{title} ajouté, configurez-le",
@@ -4151,7 +4154,9 @@
4151
4154
  "addedDescription": "{title} est sur le tableau, configurez-le ci-dessous.",
4152
4155
  "addFailedTitle": "Impossible d'ajouter le service",
4153
4156
  "servicesAddedTitle": "Services ajoutés",
4154
- "servicesAddedDescription": "{count} service ajouté au tableau. | {count} services ajoutés au tableau."
4157
+ "servicesAddedDescription": "{count} service ajouté au tableau. | {count} services ajoutés au tableau.",
4158
+ "frontendLinkedNote": "{directory} a été ajouté comme frontend et relié aux autres. Nommez ses variables d'environnement d'URL backend dans son inspecteur.",
4159
+ "frontendWiringFailedNote": "Les réglages du frontend n'ont pas été enregistrés. Ouvrez l'inspecteur de chaque application frontend pour définir son sous-répertoire et ses services backend."
4155
4160
  },
4156
4161
  "repoType": "Type de dépôt",
4157
4162
  "repoTypeHint": "Ce qu'est ce dépôt : un service backend, une application frontend, une bibliothèque partagée ou un dépôt de documentation (documents/spikes uniquement)."
@@ -4138,6 +4138,9 @@
4138
4138
  "monorepoBrowseHint": "עיין במאגר ובחר את הספריות של השירותים שברצונך להוסיף — מכל תיקייה. סוכנים העובדים על שירות ירוצו בתוך תת-הספרייה שלו.",
4139
4139
  "selectedServices": "שירותים נבחרים",
4140
4140
  "noServicesSelected": "עדיין לא נבחרו שירותים. בחר ספריות למעלה.",
4141
+ "frontendLabel": "אפליקציית פרונט-אנד (אופציונלי)",
4142
+ "frontendHint": "סמן אחת מהספריות שנבחרו כפרונט-אנד עבור השאר. היא תתווסף כאפליקציית פרונט-אנד המוצמדת לתת-הספרייה הזו ותקושר לכל שירות בק-אנד שנוסף לצידה. את שמות משתני הסביבה של כתובות הבק-אנד הגדר לאחר מכן באינספקטור של הפרונט-אנד.",
4143
+ "frontendNone": "ללא: כל הבחירות הן שירותי בק-אנד",
4141
4144
  "addServices": "הוסף שירות אחד | הוסף שני שירותים | הוסף {count} שירותים",
4142
4145
  "removeService": "הסר {directory}",
4143
4146
  "addedConfigure": "{title} נוסף, הגדר אותו",
@@ -4151,7 +4154,9 @@
4151
4154
  "addedDescription": "{title} על הלוח, הגדר אותו למטה.",
4152
4155
  "addFailedTitle": "לא ניתן היה להוסיף שירות",
4153
4156
  "servicesAddedTitle": "השירותים נוספו",
4154
- "servicesAddedDescription": "שירות אחד נוסף ללוח. | שני שירותים נוספו ללוח. | {count} שירותים נוספו ללוח."
4157
+ "servicesAddedDescription": "שירות אחד נוסף ללוח. | שני שירותים נוספו ללוח. | {count} שירותים נוספו ללוח.",
4158
+ "frontendLinkedNote": "{directory} נוספה כפרונט-אנד וקושרה לשאר. הגדר את שמות משתני הסביבה של כתובות הבק-אנד באינספקטור שלה.",
4159
+ "frontendWiringFailedNote": "הגדרות הפרונט-אנד לא נשמרו. פתח את האינספקטור של כל אפליקציית פרונט-אנד כדי להגדיר את תת-הספרייה ואת שירותי הבק-אנד שלה."
4155
4160
  },
4156
4161
  "repoType": "סוג המאגר",
4157
4162
  "repoTypeHint": "מה המאגר הזה: שירות בק-אנד, אפליקציית פרונט-אנד, ספרייה משותפת או מאגר תיעוד (מסמכים/ספייקים בלבד)."
@@ -3403,6 +3403,9 @@
3403
3403
  "monorepoBrowseHint": "Sfoglia il repository e seleziona le directory dei servizi che vuoi aggiungere, da qualsiasi cartella. Gli agenti che lavorano su un servizio verranno eseguiti all'interno della sua sottodirectory.",
3404
3404
  "selectedServices": "Servizi selezionati",
3405
3405
  "noServicesSelected": "Nessun servizio ancora selezionato. Scegli le directory sopra.",
3406
+ "frontendLabel": "App frontend (facoltativo)",
3407
+ "frontendHint": "Indica una delle directory selezionate come frontend delle altre. Viene aggiunta come app frontend ancorata a quella sottodirectory e collegata a ogni servizio backend aggiunto accanto. Assegna poi il nome a ciascuna variabile d'ambiente con l'URL del backend nell'inspector del frontend.",
3408
+ "frontendNone": "Nessuna: tutte le selezioni sono servizi backend",
3406
3409
  "addServices": "Aggiungi {count} servizio | Aggiungi {count} servizi",
3407
3410
  "removeService": "Rimuovi {directory}",
3408
3411
  "addedConfigure": "{title} aggiunto, configuralo",
@@ -3416,7 +3419,9 @@
3416
3419
  "addedDescription": "{title} è sulla board, configuralo qui sotto.",
3417
3420
  "addFailedTitle": "Impossibile aggiungere il servizio",
3418
3421
  "servicesAddedTitle": "Servizi aggiunti",
3419
- "servicesAddedDescription": "{count} servizio aggiunto alla board. | {count} servizi aggiunti alla board."
3422
+ "servicesAddedDescription": "{count} servizio aggiunto alla board. | {count} servizi aggiunti alla board.",
3423
+ "frontendLinkedNote": "{directory} è stata aggiunta come frontend e collegata alle altre. Assegna il nome alle sue variabili d'ambiente con gli URL dei backend nel suo inspector.",
3424
+ "frontendWiringFailedNote": "Le impostazioni del frontend non sono state salvate. Apri l'inspector di ogni app frontend per impostarne la sottodirectory e i servizi backend."
3420
3425
  }
3421
3426
  },
3422
3427
  "repoTree": {
@@ -4138,6 +4138,9 @@
4138
4138
  "monorepoBrowseHint": "リポジトリを参照し、追加したいサービスのディレクトリを任意のフォルダから選択してください。サービスで作業するエージェントは、そのサブディレクトリ内で実行されます。",
4139
4139
  "selectedServices": "選択したサービス",
4140
4140
  "noServicesSelected": "サービスがまだ選択されていません。上でディレクトリを選択してください。",
4141
+ "frontendLabel": "フロントエンドアプリ(任意)",
4142
+ "frontendHint": "選択したディレクトリのいずれか1つを、残りのフロントエンドとして指定します。そのサブディレクトリに固定されたフロントエンドアプリとして追加され、一緒に追加される各バックエンドサービスにリンクされます。バックエンドURLの環境変数名は、あとでフロントエンドのインスペクターで設定してください。",
4143
+ "frontendNone": "なし: 選択はすべてバックエンドサービス",
4141
4144
  "addServices": "{count}件のサービスを追加 | {count}件のサービスを追加",
4142
4145
  "removeService": "{directory} を削除",
4143
4146
  "addedConfigure": "{title}を追加しました。設定してください",
@@ -4151,7 +4154,9 @@
4151
4154
  "addedDescription": "{title}がボードに追加されました。以下で設定してください。",
4152
4155
  "addFailedTitle": "サービスを追加できませんでした",
4153
4156
  "servicesAddedTitle": "サービスを追加しました",
4154
- "servicesAddedDescription": "{count}件のサービスをボードに追加しました。 | {count}件のサービスをボードに追加しました。"
4157
+ "servicesAddedDescription": "{count}件のサービスをボードに追加しました。 | {count}件のサービスをボードに追加しました。",
4158
+ "frontendLinkedNote": "{directory} をフロントエンドとして追加し、ほかのサービスにリンクしました。バックエンドURLの環境変数名はインスペクターで設定してください。",
4159
+ "frontendWiringFailedNote": "フロントエンドの設定を保存できませんでした。各フロントエンドアプリのインスペクターを開き、サブディレクトリとバックエンドサービスを設定してください。"
4155
4160
  },
4156
4161
  "repoType": "リポジトリの種類",
4157
4162
  "repoTypeHint": "このリポジトリの種類: バックエンドサービス、フロントエンドアプリ、共有ライブラリ、またはドキュメントリポジトリ(ドキュメント/スパイクのみ)。"
@@ -4138,6 +4138,9 @@
4138
4138
  "monorepoBrowseHint": "Przeglądaj repozytorium i wybierz katalogi usług, które chcesz dodać — z dowolnego folderu. Agenci pracujący nad usługą działają w obrębie jej podkatalogu.",
4139
4139
  "selectedServices": "Wybrane usługi",
4140
4140
  "noServicesSelected": "Nie wybrano jeszcze żadnych usług. Wybierz katalogi powyżej.",
4141
+ "frontendLabel": "Aplikacja frontendowa (opcjonalnie)",
4142
+ "frontendHint": "Oznacz jeden z wybranych katalogów jako frontend dla pozostałych. Zostanie dodany jako aplikacja frontendowa przypięta do tego podkatalogu i powiązany z każdą usługą backendową dodaną obok. Nazwy zmiennych środowiskowych z adresami URL backendów uzupełnij następnie w inspektorze frontendu.",
4143
+ "frontendNone": "Brak: wszystkie wybrane pozycje to usługi backendowe",
4141
4144
  "addServices": "Dodaj {count} usługę | Dodaj {count} usługi | Dodaj {count} usług",
4142
4145
  "removeService": "Usuń {directory}",
4143
4146
  "addedConfigure": "Dodano {title}, skonfiguruj",
@@ -4151,7 +4154,9 @@
4151
4154
  "addedDescription": "{title} jest na tablicy, skonfiguruj ją poniżej.",
4152
4155
  "addFailedTitle": "Nie udało się dodać usługi",
4153
4156
  "servicesAddedTitle": "Dodano usługi",
4154
- "servicesAddedDescription": "Dodano {count} usługę do tablicy. | Dodano {count} usługi do tablicy. | Dodano {count} usług do tablicy."
4157
+ "servicesAddedDescription": "Dodano {count} usługę do tablicy. | Dodano {count} usługi do tablicy. | Dodano {count} usług do tablicy.",
4158
+ "frontendLinkedNote": "{directory} dodano jako frontend i powiązano z pozostałymi. Nazwy jego zmiennych środowiskowych z adresami URL backendów uzupełnij w inspektorze.",
4159
+ "frontendWiringFailedNote": "Ustawienia frontendu nie zostały zapisane. Otwórz inspektor każdej aplikacji frontendowej, aby ustawić jej podkatalog i usługi backendowe."
4155
4160
  },
4156
4161
  "repoType": "Typ repozytorium",
4157
4162
  "repoTypeHint": "Czym jest to repozytorium: usługą backendową, aplikacją frontendową, współdzieloną biblioteką lub repozytorium dokumentacji (tylko dokumenty/spike'i)."
@@ -4138,6 +4138,9 @@
4138
4138
  "monorepoBrowseHint": "Depoyu inceleyin ve eklemek istediğiniz servislerin dizinlerini herhangi bir klasörden seçin. Bir servis üzerinde çalışan ajanlar onun alt dizininde çalışır.",
4139
4139
  "selectedServices": "Seçili servisler",
4140
4140
  "noServicesSelected": "Henüz servis seçilmedi. Yukarıdan dizin seçin.",
4141
+ "frontendLabel": "Frontend uygulaması (isteğe bağlı)",
4142
+ "frontendHint": "Seçtiğiniz dizinlerden birini diğerlerinin frontend uygulaması olarak işaretleyin. O alt dizine sabitlenmiş bir frontend uygulaması olarak eklenir ve yanında eklenen her backend servisine bağlanır. Backend URL ortam değişkenlerinin adlarını sonrasında frontend’in inspector’ında girin.",
4143
+ "frontendNone": "Yok: tüm seçimler backend servisidir",
4141
4144
  "addServices": "{count} servis ekle | {count} servis ekle",
4142
4145
  "removeService": "{directory} öğesini kaldır",
4143
4146
  "addedConfigure": "{title} eklendi, yapılandırın",
@@ -4151,7 +4154,9 @@
4151
4154
  "addedDescription": "{title} panoda, aşağıdan yapılandırın.",
4152
4155
  "addFailedTitle": "Servis eklenemedi",
4153
4156
  "servicesAddedTitle": "Servisler eklendi",
4154
- "servicesAddedDescription": "{count} servis panoya eklendi. | {count} servis panoya eklendi."
4157
+ "servicesAddedDescription": "{count} servis panoya eklendi. | {count} servis panoya eklendi.",
4158
+ "frontendLinkedNote": "{directory} frontend olarak eklendi ve diğerlerine bağlandı. Backend URL ortam değişkenlerinin adlarını inspector’ında girin.",
4159
+ "frontendWiringFailedNote": "Frontend ayarları kaydedilmedi. Alt dizinini ve backend servislerini ayarlamak için her frontend uygulamasının inspector’ını açın."
4155
4160
  },
4156
4161
  "repoType": "Depo türü",
4157
4162
  "repoTypeHint": "Bu deponun türü: bir backend servisi, bir frontend uygulaması, paylaşılan bir kütüphane veya bir doküman deposu (yalnızca doküman/spike)."
@@ -4138,6 +4138,9 @@
4138
4138
  "monorepoBrowseHint": "Перегляньте репозиторій і виберіть каталоги сервісів, які хочете додати — з будь-якої папки. Агенти, що працюють над сервісом, виконуються в межах його підкаталогу.",
4139
4139
  "selectedServices": "Вибрані сервіси",
4140
4140
  "noServicesSelected": "Сервіси ще не вибрано. Виберіть каталоги вище.",
4141
+ "frontendLabel": "Фронтенд-застосунок (необовʼязково)",
4142
+ "frontendHint": "Позначте один із вибраних каталогів як фронтенд для решти. Його буде додано як фронтенд-застосунок, закріплений за цим підкаталогом, і повʼязано з кожним бекенд-сервісом, доданим поруч. Назви змінних середовища з URL бекендів потім вкажіть в інспекторі фронтенду.",
4143
+ "frontendNone": "Немає: усі вибрані позиції є бекенд-сервісами",
4141
4144
  "addServices": "Додати {count} сервіс | Додати {count} сервіси | Додати {count} сервісів",
4142
4145
  "removeService": "Видалити {directory}",
4143
4146
  "addedConfigure": "{title} додано, налаштуйте",
@@ -4151,7 +4154,9 @@
4151
4154
  "addedDescription": "{title} на дошці, налаштуйте його нижче.",
4152
4155
  "addFailedTitle": "Не вдалося додати сервіс",
4153
4156
  "servicesAddedTitle": "Сервіси додано",
4154
- "servicesAddedDescription": "Додано {count} сервіс до дошки. | Додано {count} сервіси до дошки. | Додано {count} сервісів до дошки."
4157
+ "servicesAddedDescription": "Додано {count} сервіс до дошки. | Додано {count} сервіси до дошки. | Додано {count} сервісів до дошки.",
4158
+ "frontendLinkedNote": "{directory} додано як фронтенд і повʼязано з рештою. Вкажіть назви його змінних середовища з URL бекендів в інспекторі.",
4159
+ "frontendWiringFailedNote": "Налаштування фронтенду не збережено. Відкрийте інспектор кожного фронтенд-застосунку, щоб указати його підкаталог і бекенд-сервіси."
4155
4160
  },
4156
4161
  "repoType": "Тип репозиторію",
4157
4162
  "repoTypeHint": "Що це за репозиторій: бекенд-сервіс, фронтенд-застосунок, спільна бібліотека або репозиторій документації (лише документи/спайки)."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.259.3",
3
+ "version": "0.260.1",
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.41",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.290.0"
43
+ "@cat-factory/contracts": "0.291.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",