@cat-factory/app 0.238.0 → 0.239.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.
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, watch } from 'vue'
3
3
  import { onKeyStroke } from '@vueuse/core'
4
+ import { lastCompleteRollupDay } from '@cat-factory/contracts'
4
5
  import type {
5
6
  ReportActivityDimension,
6
7
  ReportActivityRow,
@@ -89,6 +90,29 @@ const activityByDimension = computed<ReportActivityRow[]>(() => {
89
90
  return activity.byTaskType
90
91
  })
91
92
 
93
+ const DAY_MS = 24 * 60 * 60 * 1000
94
+
95
+ // How the window's SPEND half was answered. The long (TCO) windows read the durable
96
+ // cost-attribution rollup, which is only as fresh as the last retention sweep, so a rollup
97
+ // that has materialised NOTHING must not render as a quiet quarter and one whose watermark is
98
+ // well behind `now` must not render its empty tail as thrift. Same three states as the
99
+ // operator dashboard's daily run rollup.
100
+ //
101
+ // The lag is measured against `lastCompleteRollupDay(generatedAt)`, NOT against `generatedAt`
102
+ // itself, because that is what `rolledUpThrough` counts in: the newest day the sweep could
103
+ // possibly have finished by now. Measuring against the wall clock instead compares a day
104
+ // boundary with an instant, so the very same healthy rollup drifts from ~0h of apparent lag
105
+ // just after midnight to ~24h just before the next one, and any fixed threshold then turns the
106
+ // hour the report happened to be opened into a health verdict. One whole missed day of slack
107
+ // is deliberate: the sweep is a daily cron on one facade, so a single skipped firing is a
108
+ // hiccup the next pass heals, while two in a row is the wedge worth naming.
109
+ const rollupState = computed<'none' | 'stale' | 'current' | null>(() => {
110
+ const v = view.value
111
+ if (!v || v.source !== 'daily-rollup') return null
112
+ if (v.rolledUpThrough == null) return 'none'
113
+ return lastCompleteRollupDay(v.generatedAt) - v.rolledUpThrough > DAY_MS ? 'stale' : 'current'
114
+ })
115
+
92
116
  const maxTrend = computed(() => maxOf(view.value?.trend.points ?? [], trendMagnitude))
93
117
  const hasSpend = computed(() => (view.value?.totals.calls ?? 0) > 0)
94
118
  // Hoisted: every activity bar is scaled against the busiest slice in the SAME list, so this
@@ -246,6 +270,38 @@ watch(
246
270
  }}
247
271
  </p>
248
272
 
273
+ <!-- Which store answered, and how far it reaches. An un-materialised rollup and an
274
+ account that spent nothing produce the same empty breakdown. -->
275
+ <p
276
+ v-if="rollupState === 'none'"
277
+ class="rounded-lg border border-amber-800/60 bg-amber-950/30 px-3 py-2 text-xs text-amber-200"
278
+ data-testid="reports-rollup-none"
279
+ >
280
+ {{ t('reports.rollup.none') }}
281
+ </p>
282
+ <p
283
+ v-else-if="rollupState === 'stale'"
284
+ class="rounded-lg border border-amber-800/60 bg-amber-950/30 px-3 py-2 text-xs text-amber-200"
285
+ data-testid="reports-rollup-stale"
286
+ >
287
+ {{
288
+ t('reports.rollup.stale', {
289
+ date: d(new Date(view.rolledUpThrough ?? 0), 'short'),
290
+ })
291
+ }}
292
+ </p>
293
+ <p
294
+ v-else-if="rollupState === 'current'"
295
+ class="text-[11px] text-slate-500"
296
+ data-testid="reports-rollup-current"
297
+ >
298
+ {{
299
+ t('reports.rollup.current', {
300
+ date: d(new Date(view.rolledUpThrough ?? 0), 'short'),
301
+ })
302
+ }}
303
+ </p>
304
+
249
305
  <!-- Headline totals. A stat tile, not a chart: these are single numbers. -->
250
306
  <section>
251
307
  <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-500">
@@ -352,10 +408,11 @@ watch(
352
408
  </section>
353
409
  </div>
354
410
 
355
- <!-- The TCO axes: what a repository and a ticket actually cost. Spend-only, like the
356
- pair above, because a run's activity is already sliced by the service that owns
357
- the repo and there is no second population to pair a ticket with. -->
358
- <div class="grid gap-6 md:grid-cols-2">
411
+ <!-- The TCO axes: what a repository, a ticket and a single run actually cost.
412
+ Spend-only, like the pair above, because a run's activity is already sliced by
413
+ the service that owns the repo and there is no second population to pair a
414
+ ticket with, and a run IS the unit activity counts. -->
415
+ <div class="grid gap-6 md:grid-cols-3">
359
416
  <section>
360
417
  <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-500">
361
418
  {{ t('reports.spend.byRepo') }}
@@ -378,6 +435,17 @@ watch(
378
435
  :label-of="sliceLabel"
379
436
  />
380
437
  </section>
438
+ <section>
439
+ <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-500">
440
+ {{ t('reports.spend.byRun') }}
441
+ </h2>
442
+ <ReportsSpendBreakdown
443
+ :rows="view.spend.byRun"
444
+ :currency="currency"
445
+ test-id="reports-spend-run"
446
+ :label-of="sliceLabel"
447
+ />
448
+ </section>
381
449
  </div>
382
450
 
383
451
  <!-- The shared axis: spend AND activity for the same grouping, side by side. -->
@@ -3640,6 +3640,11 @@
3640
3640
  "retry": "Erneut versuchen",
3641
3641
  "error": "Berichte konnten nicht geladen werden.",
3642
3642
  "period": "{from} bis {to}",
3643
+ "rollup": {
3644
+ "none": "Die dauerhafte Kostenaggregation hat noch nichts erzeugt. Dieses Zeitfenster ist also mangels Daten leer, nicht mangels Kosten.",
3645
+ "stale": "Die dauerhafte Kostenaggregation reicht nur bis {date}. Alles danach sind fehlende Daten, keine günstige Woche.",
3646
+ "current": "Aus der dauerhaften Kostenaggregation, vollständig bis {date}. Die Zuordnung zu Repository, Ticket und Lauf ist hier die, die beim Entstehen der Kosten erfasst wurde."
3647
+ },
3643
3648
  "unattributed": "Nicht zugeordnet",
3644
3649
  "window": {
3645
3650
  "oneDay": "Letzte 24 Stunden",
@@ -3682,6 +3687,7 @@
3682
3687
  "byAgentKind": "Kosten nach Agententyp",
3683
3688
  "byRepo": "Kosten nach Repository",
3684
3689
  "byTicket": "Kosten nach Ticket",
3690
+ "byRun": "Kosten nach Lauf",
3685
3691
  "heading": "Kosten",
3686
3692
  "empty": "In diesem Zeitraum wurde keine Nutzung erfasst.",
3687
3693
  "calls": "{count} Aufruf | {count} Aufrufe",
@@ -1875,6 +1875,11 @@
1875
1875
  "retry": "Retry",
1876
1876
  "error": "Reports could not be loaded.",
1877
1877
  "period": "{from} to {to}",
1878
+ "rollup": {
1879
+ "none": "The durable cost rollup has produced nothing yet, so this window is empty for lack of data, not for lack of spend.",
1880
+ "stale": "The durable cost rollup only reaches {date}. Anything after that is missing data, not a cheap week.",
1881
+ "current": "From the durable cost rollup, complete through {date}. Repository, ticket and run attribution here is the one recorded when the spend happened."
1882
+ },
1878
1883
  "unattributed": "Unattributed",
1879
1884
  "window": {
1880
1885
  "oneDay": "Last 24 hours",
@@ -1917,6 +1922,7 @@
1917
1922
  "byAgentKind": "Spend by agent kind",
1918
1923
  "byRepo": "Spend by repository",
1919
1924
  "byTicket": "Spend by ticket",
1925
+ "byRun": "Spend by run",
1920
1926
  "heading": "Spend",
1921
1927
  "empty": "No recorded usage in this window.",
1922
1928
  "calls": "{count} call | {count} calls",
@@ -1777,6 +1777,11 @@
1777
1777
  "retry": "Reintentar",
1778
1778
  "error": "No se pudieron cargar los informes.",
1779
1779
  "period": "Del {from} al {to}",
1780
+ "rollup": {
1781
+ "none": "La agregación de costes duradera aún no ha producido nada, así que esta ventana está vacía por falta de datos, no por falta de gasto.",
1782
+ "stale": "La agregación de costes duradera solo llega hasta {date}. Lo posterior son datos que faltan, no una semana barata.",
1783
+ "current": "De la agregación de costes duradera, completa hasta {date}. La atribución a repositorio, tique y ejecución es la que se registró cuando se produjo el gasto."
1784
+ },
1780
1785
  "unattributed": "Sin atribuir",
1781
1786
  "window": {
1782
1787
  "oneDay": "Últimas 24 horas",
@@ -1819,6 +1824,7 @@
1819
1824
  "byAgentKind": "Gasto por tipo de agente",
1820
1825
  "byRepo": "Gasto por repositorio",
1821
1826
  "byTicket": "Gasto por tique",
1827
+ "byRun": "Gasto por ejecución",
1822
1828
  "heading": "Gasto",
1823
1829
  "empty": "No se registró uso en este periodo.",
1824
1830
  "calls": "{count} llamada | {count} llamadas",
@@ -1777,6 +1777,11 @@
1777
1777
  "retry": "Réessayer",
1778
1778
  "error": "Impossible de charger les rapports.",
1779
1779
  "period": "Du {from} au {to}",
1780
+ "rollup": {
1781
+ "none": "L'agrégation durable des coûts n'a encore rien produit : cette fenêtre est vide faute de données, pas faute de dépenses.",
1782
+ "stale": "L'agrégation durable des coûts ne va que jusqu'au {date}. Ce qui suit correspond à des données manquantes, pas à une semaine bon marché.",
1783
+ "current": "Issu de l'agrégation durable des coûts, complète jusqu'au {date}. L'attribution au dépôt, au ticket et à l'exécution est celle enregistrée au moment de la dépense."
1784
+ },
1780
1785
  "unattributed": "Non attribué",
1781
1786
  "window": {
1782
1787
  "oneDay": "Dernières 24 heures",
@@ -1819,6 +1824,7 @@
1819
1824
  "byAgentKind": "Dépense par type d’agent",
1820
1825
  "byRepo": "Dépense par dépôt",
1821
1826
  "byTicket": "Dépense par ticket",
1827
+ "byRun": "Dépense par exécution",
1822
1828
  "heading": "Dépense",
1823
1829
  "empty": "Aucune utilisation enregistrée sur cette période.",
1824
1830
  "calls": "{count} appel | {count} appels",
@@ -1777,6 +1777,11 @@
1777
1777
  "retry": "נסה שוב",
1778
1778
  "error": "לא ניתן היה לטעון את הדוחות.",
1779
1779
  "period": "מ־{from} עד {to}",
1780
+ "rollup": {
1781
+ "none": "סיכום העלויות המתמיד עוד לא הפיק דבר, ולכן החלון הזה ריק מחוסר נתונים ולא מחוסר הוצאה.",
1782
+ "stale": "סיכום העלויות המתמיד מגיע רק עד {date}. כל מה שאחרי זה הוא נתונים חסרים, לא שבוע זול.",
1783
+ "current": "מתוך סיכום העלויות המתמיד, שלם עד {date}. הייחוס למאגר, לכרטיס ולהרצה הוא זה שנרשם בזמן ההוצאה."
1784
+ },
1780
1785
  "unattributed": "ללא שיוך",
1781
1786
  "window": {
1782
1787
  "oneDay": "24 השעות האחרונות",
@@ -1819,6 +1824,7 @@
1819
1824
  "byAgentKind": "עלות לפי סוג סוכן",
1820
1825
  "byRepo": "עלות לפי מאגר",
1821
1826
  "byTicket": "עלות לפי כרטיס",
1827
+ "byRun": "עלות לפי הרצה",
1822
1828
  "heading": "עלות",
1823
1829
  "empty": "לא נרשם שימוש בטווח הזה.",
1824
1830
  "calls": "קריאה אחת | שתי קריאות | {count} קריאות",
@@ -3640,6 +3640,11 @@
3640
3640
  "retry": "Riprova",
3641
3641
  "error": "Impossibile caricare i report.",
3642
3642
  "period": "Dal {from} al {to}",
3643
+ "rollup": {
3644
+ "none": "L'aggregazione permanente dei costi non ha ancora prodotto nulla: questa finestra è vuota per mancanza di dati, non di spesa.",
3645
+ "stale": "L'aggregazione permanente dei costi arriva solo al {date}. Quello che viene dopo sono dati mancanti, non una settimana economica.",
3646
+ "current": "Dall'aggregazione permanente dei costi, completa fino al {date}. L'attribuzione a repository, ticket ed esecuzione è quella registrata quando la spesa è avvenuta."
3647
+ },
3643
3648
  "unattributed": "Non attribuito",
3644
3649
  "window": {
3645
3650
  "oneDay": "Ultime 24 ore",
@@ -3682,6 +3687,7 @@
3682
3687
  "byAgentKind": "Spesa per tipo di agente",
3683
3688
  "byRepo": "Spesa per repository",
3684
3689
  "byTicket": "Spesa per ticket",
3690
+ "byRun": "Spesa per esecuzione",
3685
3691
  "heading": "Spesa",
3686
3692
  "empty": "Nessun utilizzo registrato in questo periodo.",
3687
3693
  "calls": "{count} chiamata | {count} chiamate",
@@ -1777,6 +1777,11 @@
1777
1777
  "retry": "再試行",
1778
1778
  "error": "レポートを読み込めませんでした。",
1779
1779
  "period": "{from} 〜 {to}",
1780
+ "rollup": {
1781
+ "none": "永続的なコスト集計はまだ何も生成していません。この期間が空なのは費用がなかったからではなく、データがないためです。",
1782
+ "stale": "永続的なコスト集計は {date} までしか届いていません。それ以降は費用が少なかったのではなく、欠けているデータです。",
1783
+ "current": "永続的なコスト集計より。{date} まで完全です。ここでのリポジトリ・チケット・実行への割り当ては、費用が発生した時点で記録されたものです。"
1784
+ },
1780
1785
  "unattributed": "未割り当て",
1781
1786
  "window": {
1782
1787
  "oneDay": "過去24時間",
@@ -1819,6 +1824,7 @@
1819
1824
  "byAgentKind": "エージェント種別の費用",
1820
1825
  "byRepo": "リポジトリ別の費用",
1821
1826
  "byTicket": "チケット別の費用",
1827
+ "byRun": "実行別の費用",
1822
1828
  "heading": "費用",
1823
1829
  "empty": "この期間に記録された利用はありません。",
1824
1830
  "calls": "{count} 件の呼び出し | {count} 件の呼び出し",
@@ -1777,6 +1777,11 @@
1777
1777
  "retry": "Spróbuj ponownie",
1778
1778
  "error": "Nie udało się wczytać raportów.",
1779
1779
  "period": "Od {from} do {to}",
1780
+ "rollup": {
1781
+ "none": "Trwałe zestawienie kosztów nie wyprodukowało jeszcze niczego, więc to okno jest puste z braku danych, a nie z braku wydatków.",
1782
+ "stale": "Trwałe zestawienie kosztów sięga tylko do {date}. To, co po tej dacie, to brakujące dane, a nie tani tydzień.",
1783
+ "current": "Z trwałego zestawienia kosztów, kompletnego do {date}. Przypisanie do repozytorium, zgłoszenia i uruchomienia jest tym zapisanym w chwili poniesienia kosztu."
1784
+ },
1780
1785
  "unattributed": "Nieprzypisane",
1781
1786
  "window": {
1782
1787
  "oneDay": "Ostatnie 24 godziny",
@@ -1819,6 +1824,7 @@
1819
1824
  "byAgentKind": "Koszty według typu agenta",
1820
1825
  "byRepo": "Koszty według repozytorium",
1821
1826
  "byTicket": "Koszty według zgłoszenia",
1827
+ "byRun": "Koszty według uruchomienia",
1822
1828
  "heading": "Koszty",
1823
1829
  "empty": "W tym okresie nie zarejestrowano użycia.",
1824
1830
  "calls": "{count} wywołanie | {count} wywołania | {count} wywołań",
@@ -1777,6 +1777,11 @@
1777
1777
  "retry": "Yeniden dene",
1778
1778
  "error": "Raporlar yüklenemedi.",
1779
1779
  "period": "{from} – {to}",
1780
+ "rollup": {
1781
+ "none": "Kalıcı maliyet toplaması henüz hiçbir şey üretmedi; bu pencere harcama olmadığı için değil, veri olmadığı için boş.",
1782
+ "stale": "Kalıcı maliyet toplaması yalnızca {date} tarihine kadar geliyor. Sonrası ucuz geçen bir hafta değil, eksik veri.",
1783
+ "current": "Kalıcı maliyet toplamasından; {date} tarihine kadar eksiksiz. Buradaki depo, bilet ve çalıştırma ataması, harcama yapıldığı anda kaydedilenidir."
1784
+ },
1780
1785
  "unattributed": "Atanmamış",
1781
1786
  "window": {
1782
1787
  "oneDay": "Son 24 saat",
@@ -1819,6 +1824,7 @@
1819
1824
  "byAgentKind": "Ajan türüne göre harcama",
1820
1825
  "byRepo": "Depoya göre harcama",
1821
1826
  "byTicket": "Bilete göre harcama",
1827
+ "byRun": "Çalıştırmaya göre harcama",
1822
1828
  "heading": "Harcama",
1823
1829
  "empty": "Bu dönemde kayıtlı kullanım yok.",
1824
1830
  "calls": "{count} çağrı | {count} çağrı",
@@ -1777,6 +1777,11 @@
1777
1777
  "retry": "Спробувати ще раз",
1778
1778
  "error": "Не вдалося завантажити звіти.",
1779
1779
  "period": "З {from} по {to}",
1780
+ "rollup": {
1781
+ "none": "Стале зведення витрат ще нічого не сформувало, тож це вікно порожнє через брак даних, а не через брак витрат.",
1782
+ "stale": "Стале зведення витрат сягає лише {date}. Усе після цієї дати означає відсутні дані, а не дешевий тиждень.",
1783
+ "current": "Зі сталого зведення витрат, повного до {date}. Прив’язку до репозиторію, тікета й запуску тут зафіксовано в момент витрати."
1784
+ },
1780
1785
  "unattributed": "Без прив’язки",
1781
1786
  "window": {
1782
1787
  "oneDay": "Останні 24 години",
@@ -1819,6 +1824,7 @@
1819
1824
  "byAgentKind": "Витрати за типом агента",
1820
1825
  "byRepo": "Витрати за репозиторієм",
1821
1826
  "byTicket": "Витрати за тікетом",
1827
+ "byRun": "Витрати за запуском",
1822
1828
  "heading": "Витрати",
1823
1829
  "empty": "За цей період використання не зафіксовано.",
1824
1830
  "calls": "{count} виклик | {count} виклики | {count} викликів",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.238.0",
3
+ "version": "0.239.0",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,7 +40,7 @@
40
40
  "valibot": "^1.4.2",
41
41
  "vue": "3.5.40",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.258.0"
43
+ "@cat-factory/contracts": "0.259.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",