@nightkatana/kronosys-app 1.0.0-beta.21 → 1.0.0-beta.22

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 (46) hide show
  1. package/README.md +1 -1
  2. package/app/changelog/page.tsx +87 -19
  3. package/app/globals.css +10 -8
  4. package/app/guide/page.tsx +71 -34
  5. package/app/implementation/page.tsx +70 -60
  6. package/app/licenses/page.tsx +79 -47
  7. package/app/logs/page.tsx +103 -47
  8. package/app/page.tsx +104 -169
  9. package/app/reporting/page.tsx +1918 -1436
  10. package/app/settings/page.tsx +66 -44
  11. package/components/KronosysPayloadProvider.tsx +19 -5
  12. package/components/dashboard/AppShellHeaderKronoFocus.tsx +78 -0
  13. package/components/dashboard/AppShellHeaderToolbarLayout.tsx +36 -0
  14. package/components/dashboard/AppShellHeaderUtilityRibbon.tsx +19 -0
  15. package/components/dashboard/AppShellHeaderWallClock.tsx +23 -17
  16. package/components/dashboard/AppShellRouteNav.tsx +336 -209
  17. package/components/dashboard/AppShellToolbarCommandCenter.tsx +225 -0
  18. package/components/dashboard/AppShellToolbarRouteNav.tsx +204 -0
  19. package/components/dashboard/DashboardCommandCenter.tsx +119 -30
  20. package/components/dashboard/KronoFocusPanel.tsx +287 -260
  21. package/components/dashboard/LanguageMenu.tsx +23 -7
  22. package/components/dashboard/PageRefreshButton.tsx +42 -16
  23. package/components/dashboard/ReportingTour.tsx +20 -2
  24. package/components/dashboard/SessionListPanel.tsx +4 -4
  25. package/components/dashboard/ThemeToggle.tsx +4 -3
  26. package/components/dashboard/useAnchoredFloatingPortalStyle.ts +9 -2
  27. package/components/dashboard/useKronoFocusLiveSeconds.ts +4 -2
  28. package/lib/appShellHeaderClasses.ts +22 -3
  29. package/lib/appShellToolbarChrome.ts +112 -0
  30. package/lib/appShellToolbarDeferredIntents.ts +112 -0
  31. package/lib/appShellToolbarSessionSlices.ts +67 -0
  32. package/lib/dashboardCopy.ts +78 -29
  33. package/lib/dashboardQuickSearch.ts +37 -6
  34. package/lib/dashboardUrlSession.ts +36 -0
  35. package/lib/generatedUserChangelog.ts +14 -0
  36. package/lib/implementationNotes.ts +18 -14
  37. package/lib/reportingAggregate.ts +68 -9
  38. package/lib/reportingMetricHelp.ts +8 -8
  39. package/lib/reportingStrings.ts +118 -9
  40. package/lib/reportingTagWeekBreakdown.ts +55 -13
  41. package/lib/settingsCopy.ts +6 -7
  42. package/lib/userGuideCopy.ts +29 -26
  43. package/package.json +7 -5
  44. package/server/db.ts +6 -4
  45. package/server/dbSchema.ts +2 -2
  46. package/components/dashboard/AppShellCommandCenterPlaceholder.tsx +0 -17
@@ -15,8 +15,32 @@ export type ReportingNavStrings = {
15
15
  guide: string;
16
16
  /** Page inventaire des user stories (`/implementation`). */
17
17
  implementation: string;
18
+ /** Journal des versions (`/changelog`). */
19
+ changelog: string;
18
20
  /** Infobulle lorsque l’icône tableau de bord pulse (rappel conflit minuteurs). */
19
21
  dashboardAttentionHint?: string;
22
+ /** Infobulle : Gantt du jour réservé au tableau de bord. */
23
+ navGanttDashboardOnlyTooltip: string;
24
+ /** Infobulle : pause globale réservée au tableau de bord (session live). */
25
+ navGlobalPauseDashboardOnlyTooltip: string;
26
+ /** Infobulle : pause globale indisponible sur le tableau de bord (archive ou chargement). */
27
+ navGlobalPauseNoSessionContextTooltip: string;
28
+ /** Infobulle : changelog indisponible (libellé vide sur cette route). */
29
+ navChangelogUnavailableTooltip: string;
30
+ /** Infobulle : implémentation indisponible (libellé vide sur cette route). */
31
+ navImplementationUnavailableTooltip: string;
32
+ /** Libellé du bouton Gantt (barre de navigation). */
33
+ navGanttButtonLabel: string;
34
+ /** Libellé du bouton pause globale (barre de navigation). */
35
+ navGlobalPauseButtonLabel: string;
36
+ };
37
+
38
+ /** Libellés passés à `AppShellRouteNav` (inclut les groupes type ruban pour l’accessibilité). */
39
+ export type AppShellRouteNavLabelBundle = ReportingNavStrings & {
40
+ navGroupAppAria: string;
41
+ navGroupDocsAria: string;
42
+ navGroupToolsAria: string;
43
+ navGroupLegalAria: string;
20
44
  };
21
45
 
22
46
  export type ReportingStrings = ReportingNavStrings &
@@ -110,6 +134,12 @@ export type ReportingStrings = ReportingNavStrings &
110
134
  projectColTime: string;
111
135
  projectUnassigned: string;
112
136
  projectTableHint: string;
137
+ /** Légende du sélecteur Travail / Personnel sur la grille par projet. */
138
+ projectScopeLegend: string;
139
+ /** aria-label du radiogroupe Travail / Personnel. */
140
+ projectScopeGroupAria: string;
141
+ projectScopeWork: string;
142
+ projectScopePersonal: string;
113
143
  /** Temps tâche par #tag — usage type affectation WBS / NWA (SAP). */
114
144
  tagTimeSectionTitle: string;
115
145
  tagTimeSectionHint: string;
@@ -175,6 +205,18 @@ export type ReportingStrings = ReportingNavStrings &
175
205
  weekNavNoProjectDataThisWeek: string;
176
206
  /** Section lignes écrites + tableaux par langage (agrégés). */
177
207
  tocLocSection: string;
208
+ /** Sommaire : entrée des trois vues (facturation / rythme / avancé). */
209
+ tocViewTabs: string;
210
+ /** Onglet — temps par #tag et @projet pour codifier la facturation. */
211
+ reportingViewTabBilling: string;
212
+ /** Onglet — graphiques d’activité par jour. */
213
+ reportingViewTabRhythm: string;
214
+ /** Onglet — indicateurs détaillés, tableau journalier, instantané workspace. */
215
+ reportingViewTabAdvanced: string;
216
+ /** aria-label du groupe d’onglets sous les filtres. */
217
+ reportingViewTabGroupAria: string;
218
+ /** Court texte d’intention sous l’onglet facturation. */
219
+ reportingViewBillingIntro: string;
178
220
  /** Bouton flottant retour en haut de page. */
179
221
  scrollToTopAria: string;
180
222
  };
@@ -185,10 +227,24 @@ const en: ReportingStrings = {
185
227
  settings: "Settings",
186
228
  logs: "Action logs",
187
229
  guide: "User guide",
230
+ changelog: "Changelog",
188
231
  implementation: "User stories — implementation",
232
+ navGanttDashboardOnlyTooltip:
233
+ "Today’s Gantt is available from the dashboard only.",
234
+ navGlobalPauseDashboardOnlyTooltip:
235
+ "Global pause is available from the dashboard when a live session is loaded.",
236
+ navGlobalPauseNoSessionContextTooltip:
237
+ "Global pause is not available while you inspect an archived session or before the live session is loaded.",
238
+ navChangelogUnavailableTooltip:
239
+ "Changelog shortcut is hidden on this screen (no label).",
240
+ navImplementationUnavailableTooltip:
241
+ "Implementation shortcut is hidden on this screen (no label).",
242
+ navGanttButtonLabel: "Open today timeline (Gantt)",
243
+ navGlobalPauseButtonLabel:
244
+ "Pause session, tasks, and subtasks in this context",
189
245
  title: "Reporting",
190
246
  subtitle:
191
- "Aggregates from Kronosys history (local API). Filter by date range and tags. Task time by @project is included.",
247
+ "See where time was allocated on the range (tags, projects) to help encode billing lines. Filters apply to history below; the workspace snapshot (if shown) is under Advanced.",
192
248
  archivedSessionsReportingNote:
193
249
  "Archived sessions: task rows, recorded task time, time by project, and per-task KronoFocus counts include only tasks marked done with every subtask checked. Session-level coding and active time still cover the whole session.",
194
250
  reportingArchivedExcludedAside:
@@ -210,9 +266,11 @@ const en: ReportingStrings = {
210
266
  filtersHelpAriaLabel: "How reporting filters apply",
211
267
  filtersHelpBody: `Where dates and tags apply on this page
212
268
 
213
- Workspace folder snapshot (lines in the open project): ignores filters not Kronosys history.
269
+ Three tabs under the filters: Billing allocation (#tags and @projects), Rhythm (per-day charts), Advanced (summary KPIs, day table, lines/signals, workspace snapshot when enabled).
214
270
 
215
- Summary KPIs, charts, day-by-day table: filters apply. Session-day side: session count, session coding/active/wall time, KronoFocus sessions completed, lines written, breakdown of sessions by closure type when the host saved one at session end, and (when the host stored a planned start) punctuality aggregates: reference count, late session count, cumulative and average late minutes. Task-day side: task rows, recorded task time, done/in-progress counts, task KronoFocus used/cycles.
271
+ Workspace folder snapshot (lines in the open project): under Advanced when code metrics are on; ignores filters not Kronosys history.
272
+
273
+ • Summary KPIs, day-by-day table: filters apply. Session-day side: session count, session coding/active/wall time, KronoFocus sessions completed, lines written, breakdown of sessions by closure type when the host saved one at session end, and (when the host stored a planned start) punctuality aggregates: reference count, late session count, cumulative and average late minutes. Task-day side: task rows, recorded task time, done/in-progress counts, task KronoFocus used/cycles.
216
274
 
217
275
  • “Lines by language” and “Coding signals” tables: session day within the range; with tags selected, a whole session is dropped if no task matches (same rule as elsewhere).
218
276
 
@@ -277,7 +335,11 @@ Archived sessions: task-based metrics only count fully completed tasks (subtasks
277
335
  projectColTime: "Recorded time",
278
336
  projectUnassigned: "(no project)",
279
337
  projectTableHint:
280
- "One line per calendar week, then one table: each @project on its own row, seven day columns and a week total — same week navigation and week-start setting as the tag calendar above.",
338
+ "One line per calendar week, then one table: each project on its own row, seven day columns and a week total — same week navigation and week-start setting as the tag calendar above. Use the Work (@) / Personal (!) control under the title to switch ledgers: Work is the default and excludes the personal ledger; Personal lists only that ledger (tasks flagged personal, or a recognized ! token in the title if the flag was not saved), with ! labels.",
339
+ projectScopeLegend: "Project ledger",
340
+ projectScopeGroupAria: "Recorded time by project: work or personal ledger",
341
+ projectScopeWork: "Work (@)",
342
+ projectScopePersonal: "Personal (!)",
281
343
  tagTimeSectionTitle: "Recorded task time by tag",
282
344
  tagTimeSectionHint:
283
345
  "Map each #tag to an external key (e.g. SAP WBS or NWA). A task’s full recorded time is counted under every tag on that task (tag totals can overlap). In the by-day table and the weekly calendar, several `project#code` tags for the same project (same day or same week) appear under one @project row—expand to see each tag line.",
@@ -337,6 +399,13 @@ Archived sessions: task-based metrics only count fully completed tasks (subtasks
337
399
  weekNavNoTagDataThisWeek: "No recorded task time for this week.",
338
400
  weekNavNoProjectDataThisWeek: "No recorded task time by project for this week.",
339
401
  tocLocSection: "Lines & coding signals",
402
+ tocViewTabs: "Views (tabs)",
403
+ reportingViewTabBilling: "Billing allocation",
404
+ reportingViewTabRhythm: "Rhythm",
405
+ reportingViewTabAdvanced: "Advanced",
406
+ reportingViewTabGroupAria: "Reporting views",
407
+ reportingViewBillingIntro:
408
+ "Map recorded time to #tags and @projects for your billing codes. Totals follow the same filters as above; open work is flagged in Filters when relevant.",
340
409
  scrollToTopAria: "Back to top",
341
410
  ...reportingMetricHelpEn,
342
411
  };
@@ -347,10 +416,24 @@ const fr: ReportingStrings = {
347
416
  settings: "Paramètres",
348
417
  logs: "Journal des actions",
349
418
  guide: "Guide d’utilisation",
419
+ changelog: "Journal des versions",
350
420
  implementation: "User stories — implémentation",
421
+ navGanttDashboardOnlyTooltip:
422
+ "Le Gantt du jour n’est disponible que depuis le tableau de bord.",
423
+ navGlobalPauseDashboardOnlyTooltip:
424
+ "La pause globale n’est disponible que depuis le tableau de bord lorsqu’une session live est chargée.",
425
+ navGlobalPauseNoSessionContextTooltip:
426
+ "La pause globale n’est pas disponible pendant la consultation d’une archive ou avant le chargement de la session live.",
427
+ navChangelogUnavailableTooltip:
428
+ "Raccourci changelog masqué sur cet écran (libellé vide).",
429
+ navImplementationUnavailableTooltip:
430
+ "Raccourci implémentation masqué sur cet écran (libellé vide).",
431
+ navGanttButtonLabel: "Ouvrir le fil de la journée (vue Gantt)",
432
+ navGlobalPauseButtonLabel:
433
+ "Mettre en pause la session, les tâches et les sous-tâches dans ce contexte",
351
434
  title: "Rapports",
352
435
  subtitle:
353
- "Agrégats à partir de lhistorique Kronosys (API locale). Filtrez par plage de dates et par étiquettes. Le temps par @projet est inclus.",
436
+ "Voir le temps sest réparti sur la période (#étiquettes, @projets) pour aider à coder les lignes de facturation. Les filtres s’appliquent à l’historique ci-dessous ; l’instantané du workspace (s’il est affiché) est sous Avancé.",
354
437
  archivedSessionsReportingNote:
355
438
  "Sessions archivées : les lignes de tâches, le temps enregistré sur les tâches, le temps par projet et les comptes KronoFocus par tâche ne retiennent que les tâches marquées terminées dont toutes les sous-tâches sont cochées. Les temps de codage et d’activité au niveau session restent ceux de la session entière.",
356
439
  reportingArchivedExcludedAside:
@@ -373,9 +456,11 @@ const fr: ReportingStrings = {
373
456
  filtersHelpAriaLabel: "Comment s’appliquent les filtres des rapports",
374
457
  filtersHelpBody: `Où agissent les dates et les étiquettes sur cette page
375
458
 
376
- Instantané « Dossier ouvert » (lignes par langage du workspace) : hors filtres ce n’est pas l’historique Kronosys.
459
+ Trois volets sous les filtres : Allocation facturation (#étiquettes et @projets), Rythme (graphiques par jour), Avancé (indicateurs de synthèse, tableau journalier, lignes / signaux, instantané workspace si activé).
460
+
461
+ • Instantané « Dossier ouvert » : sous **Avancé** lorsque les métriques code sont activées ; hors filtres — ce n’est pas l’historique Kronosys.
377
462
 
378
- • Indicateurs de synthèse, graphiques, tableau par jour : la plage et les étiquettes s’appliquent. Côté jour de session : nombre de sessions, temps de codage et d’activité, durée murale, KronoFocus « sessions terminées », lignes écrites, ventilation des sessions par type de clôture lorsque l’hôte en a enregistré un à la fin de session, et (si l’hôte a enregistré un début prévu) indicateurs d’assiduité : sessions avec référence, sessions en retard, retard cumulé, retard moyen lorsqu’en retard. Côté jour de tâche : lignes de tâches, temps enregistré, tâches terminées / en cours, KronoFocus côté tâches.
463
+ • Indicateurs de synthèse, tableau par jour : la plage et les étiquettes s’appliquent. Côté jour de session : nombre de sessions, temps de codage et d’activité, durée murale, KronoFocus « sessions terminées », lignes écrites, ventilation des sessions par type de clôture lorsque l’hôte en a enregistré un à la fin de session, et (si l’hôte a enregistré un début prévu) indicateurs d’assiduité : sessions avec référence, sessions en retard, retard cumulé, retard moyen lorsqu’en retard. Côté jour de tâche : lignes de tâches, temps enregistré, tâches terminées / en cours, KronoFocus côté tâches.
379
464
 
380
465
  • Tableaux « Lignes par langage » et « Signaux de codage » : jour de session dans la plage ; avec étiquettes sélectionnées, toute la session est exclue si aucune tâche ne correspond (même règle qu’ailleurs).
381
466
 
@@ -441,7 +526,11 @@ Sessions archivées : pour les métriques basées sur les tâches, seules les t
441
526
  projectColTime: "Temps enregistré",
442
527
  projectUnassigned: "(sans projet)",
443
528
  projectTableHint:
444
- "Une ligne par semaine calendaire, puis un tableau : chaque @projet sur sa propre ligne, sept colonnes jour et le total de la semaine — même navigation par semaine et même premier jour de semaine que le calendrier par étiquette ci-dessus.",
529
+ "Une ligne par semaine calendaire, puis un tableau : chaque projet sur sa propre ligne, sept colonnes jour et le total de la semaine — même navigation par semaine et même premier jour de semaine que le calendrier par étiquette ci-dessus. Utilisez le contrôle Travail (@) / Personnel (!) sous le titre pour changer de registre : Travail (défaut) exclut le registre personnel ; Personnel n’affiche que celui-ci (tâches marquées personnelles, ou titre avec jeton ! reconnu si le drapeau n’a pas été enregistré), avec le préfixe « ! ».",
530
+ projectScopeLegend: "Registre projet",
531
+ projectScopeGroupAria: "Temps par projet : registre travail ou personnel",
532
+ projectScopeWork: "Travail (@)",
533
+ projectScopePersonal: "Personnel (!)",
445
534
  tagTimeSectionTitle: "Temps enregistré par étiquette",
446
535
  tagTimeSectionHint:
447
536
  "Chaque #tag peut correspondre à une clé externe (ex. poste WBS ou NWA dans SAP). La durée enregistrée complète d’une tâche est comptée pour chaque étiquette associée (les totaux par étiquette peuvent se chevaucher). Dans le tableau par jour et le calendrier hebdomadaire, plusieurs étiquettes `projet#code` pour un même projet (même jour ou même semaine) sont regroupées sous une ligne @projet : développez-la pour voir chaque ligne d’étiquette.",
@@ -502,6 +591,13 @@ Sessions archivées : pour les métriques basées sur les tâches, seules les t
502
591
  weekNavNoTagDataThisWeek: "Aucun temps de tâche enregistré pour cette semaine.",
503
592
  weekNavNoProjectDataThisWeek: "Aucun temps de tâche par projet pour cette semaine.",
504
593
  tocLocSection: "Lignes et signaux de codage",
594
+ tocViewTabs: "Vues (onglets)",
595
+ reportingViewTabBilling: "Allocation facturation",
596
+ reportingViewTabRhythm: "Rythme",
597
+ reportingViewTabAdvanced: "Avancé",
598
+ reportingViewTabGroupAria: "Vues des rapports",
599
+ reportingViewBillingIntro:
600
+ "Associez le temps enregistré aux #étiquettes et aux @projets pour vos codes de facturation. Les totaux suivent les mêmes filtres qu’en haut ; le travail encore ouvert est rappelé dans Filtres le cas échéant.",
505
601
  scrollToTopAria: "Retour en haut de la page",
506
602
  ...reportingMetricHelpFr,
507
603
  };
@@ -510,7 +606,7 @@ export function reportingStrings(lang: Lang): ReportingStrings {
510
606
  return lang === "fr" ? fr : en;
511
607
  }
512
608
 
513
- export function reportingNav(lang: Lang): ReportingNavStrings {
609
+ export function reportingNav(lang: Lang): AppShellRouteNavLabelBundle {
514
610
  const s = reportingStrings(lang);
515
611
  const d = dashboardStrings(lang);
516
612
  return {
@@ -520,6 +616,19 @@ export function reportingNav(lang: Lang): ReportingNavStrings {
520
616
  logs: s.logs,
521
617
  guide: s.guide,
522
618
  implementation: s.implementation,
619
+ changelog: s.changelog,
523
620
  dashboardAttentionHint: d.navDashboardPlannedConflictPulseHint,
621
+ navGanttDashboardOnlyTooltip: s.navGanttDashboardOnlyTooltip,
622
+ navGlobalPauseDashboardOnlyTooltip: s.navGlobalPauseDashboardOnlyTooltip,
623
+ navGlobalPauseNoSessionContextTooltip:
624
+ s.navGlobalPauseNoSessionContextTooltip,
625
+ navChangelogUnavailableTooltip: s.navChangelogUnavailableTooltip,
626
+ navImplementationUnavailableTooltip: s.navImplementationUnavailableTooltip,
627
+ navGanttButtonLabel: s.navGanttButtonLabel,
628
+ navGlobalPauseButtonLabel: s.navGlobalPauseButtonLabel,
629
+ navGroupAppAria: d.appShellRouteNavGroupAppAria,
630
+ navGroupDocsAria: d.appShellRouteNavGroupDocsAria,
631
+ navGroupToolsAria: d.appShellRouteNavGroupToolsAria,
632
+ navGroupLegalAria: d.appShellRouteNavGroupLegalAria,
524
633
  };
525
634
  }
@@ -8,11 +8,28 @@ import {
8
8
  parseProjectScopedTag,
9
9
  } from "@/lib/taskParsing";
10
10
 
11
+ /** Registre projet pour une clé d’étiquette `…#…` déjà canonique (`@` / `!`). */
12
+ function reportingScopedTagLedgerFromKey(tagKey: string): "p" | "w" {
13
+ return tagKey.trim().toLowerCase().startsWith("!") ? "p" : "w";
14
+ }
15
+
16
+ function scopedRollupBucketId(tagKey: string): string | null {
17
+ const scoped = parseProjectScopedTag(tagKey);
18
+ if (!scoped) {
19
+ return null;
20
+ }
21
+ const pk = normalizeProjectKey(scoped.projectKey).toLowerCase();
22
+ return `${pk}:::${reportingScopedTagLedgerFromKey(tagKey)}`;
23
+ }
24
+
11
25
  export type TagWeekDisplayLeaf = { kind: "leaf"; row: TagWeekCalendarRow };
12
26
 
13
27
  export type TagWeekDisplayRollup = {
14
28
  kind: "rollup";
29
+ /** Projet normalisé (sans @ / !), pour descriptions et clés métier. */
15
30
  projectKeyLower: string;
31
+ /** Clef stable pour l’état replié (registre @ vs ! séparés). */
32
+ rollupStableKey: string;
16
33
  displayProject: string;
17
34
  weekStart: string;
18
35
  parentSlots: number[];
@@ -34,7 +51,7 @@ function sumSlots(rows: readonly TagWeekCalendarRow[]): number[] {
34
51
 
35
52
  /**
36
53
  * Prépare les lignes du calendrier hebdo par étiquette : étiquettes globales et sans étiquette telles quelles ;
37
- * les étiquettes `projet#suffixe` (plusieurs pour le même projet) deviennent un bloc repliable avec total par projet.
54
+ * les étiquettes `projet#suffixe` deviennent un bloc repliable avec total par projet **et par registre** (`@` vs `!` : pas de fusion entre travail et personnel pour le même nom de projet).
38
55
  */
39
56
  export function buildTagWeekDisplayBlocks(rows: readonly TagWeekCalendarRow[]): TagWeekDisplayBlock[] {
40
57
  const scopedBuckets = new Map<string, TagWeekCalendarRow[]>();
@@ -51,10 +68,14 @@ export function buildTagWeekDisplayBlocks(rows: readonly TagWeekCalendarRow[]):
51
68
  globals.push(row);
52
69
  continue;
53
70
  }
54
- const pk = normalizeProjectKey(scoped.projectKey).toLowerCase();
55
- const list = scopedBuckets.get(pk) ?? [];
71
+ const bucketId = scopedRollupBucketId(row.tagKey);
72
+ if (!bucketId) {
73
+ globals.push(row);
74
+ continue;
75
+ }
76
+ const list = scopedBuckets.get(bucketId) ?? [];
56
77
  list.push(row);
57
- scopedBuckets.set(pk, list);
78
+ scopedBuckets.set(bucketId, list);
58
79
  }
59
80
 
60
81
  globals.sort((a, b) => normalizeTagKey(a.tagKey).localeCompare(normalizeTagKey(b.tagKey)));
@@ -65,20 +86,28 @@ export function buildTagWeekDisplayBlocks(rows: readonly TagWeekCalendarRow[]):
65
86
  }
66
87
 
67
88
  const projKeys = [...scopedBuckets.keys()].sort((a, b) => a.localeCompare(b));
68
- for (const pk of projKeys) {
69
- const children = [...scopedBuckets.get(pk)!].sort((a, b) =>
89
+ for (const rollupStableKey of projKeys) {
90
+ const children = [...scopedBuckets.get(rollupStableKey)!].sort((a, b) =>
70
91
  normalizeTagKey(a.tagKey).localeCompare(normalizeTagKey(b.tagKey))
71
92
  );
93
+ const pk =
94
+ rollupStableKey.includes(":::")
95
+ ? rollupStableKey.slice(0, rollupStableKey.indexOf(":::"))
96
+ : rollupStableKey;
72
97
  if (children.length === 1) {
73
98
  blocks.push({ kind: "leaf", row: children[0] });
74
99
  continue;
75
100
  }
76
101
  const first = parseProjectScopedTag(children[0].tagKey);
77
- const displayProject = first ? formatProjectDisplay(first.projectKey) : pk;
102
+ const personalRollup = reportingScopedTagLedgerFromKey(children[0].tagKey) === "p";
103
+ const displayProject = first
104
+ ? formatProjectDisplay(first.projectKey, personalRollup ? { personal: true } : undefined)
105
+ : pk;
78
106
  const weekStart = children[0].weekStart;
79
107
  blocks.push({
80
108
  kind: "rollup",
81
109
  projectKeyLower: pk,
110
+ rollupStableKey,
82
111
  displayProject,
83
112
  weekStart,
84
113
  parentSlots: sumSlots(children),
@@ -99,6 +128,7 @@ export type TagDayDisplayLeaf = { kind: "leaf"; row: ReportingTagTimeDayRow };
99
128
  export type TagDayDisplayRollup = {
100
129
  kind: "rollup";
101
130
  projectKeyLower: string;
131
+ rollupStableKey: string;
102
132
  displayProject: string;
103
133
  day: string;
104
134
  parentMinutes: number;
@@ -122,10 +152,14 @@ function buildTagDaySegmentBlocks(segment: readonly ReportingTagTimeDayRow[]): T
122
152
  globals.push(row);
123
153
  continue;
124
154
  }
125
- const pk = normalizeProjectKey(scoped.projectKey).toLowerCase();
126
- const list = scopedBuckets.get(pk) ?? [];
155
+ const bucketId = scopedRollupBucketId(row.tagKey);
156
+ if (!bucketId) {
157
+ globals.push(row);
158
+ continue;
159
+ }
160
+ const list = scopedBuckets.get(bucketId) ?? [];
127
161
  list.push(row);
128
- scopedBuckets.set(pk, list);
162
+ scopedBuckets.set(bucketId, list);
129
163
  }
130
164
 
131
165
  globals.sort((a, b) => normalizeTagKey(a.tagKey).localeCompare(normalizeTagKey(b.tagKey)));
@@ -136,21 +170,29 @@ function buildTagDaySegmentBlocks(segment: readonly ReportingTagTimeDayRow[]): T
136
170
  }
137
171
 
138
172
  const projKeys = [...scopedBuckets.keys()].sort((a, b) => a.localeCompare(b));
139
- for (const pk of projKeys) {
140
- const children = [...scopedBuckets.get(pk)!].sort((a, b) =>
173
+ for (const rollupStableKey of projKeys) {
174
+ const children = [...scopedBuckets.get(rollupStableKey)!].sort((a, b) =>
141
175
  normalizeTagKey(a.tagKey).localeCompare(normalizeTagKey(b.tagKey))
142
176
  );
177
+ const pk =
178
+ rollupStableKey.includes(":::")
179
+ ? rollupStableKey.slice(0, rollupStableKey.indexOf(":::"))
180
+ : rollupStableKey;
143
181
  if (children.length === 1) {
144
182
  blocks.push({ kind: "leaf", row: children[0] });
145
183
  continue;
146
184
  }
147
185
  const first = parseProjectScopedTag(children[0].tagKey);
148
- const displayProject = first ? formatProjectDisplay(first.projectKey) : pk;
186
+ const personalRollup = reportingScopedTagLedgerFromKey(children[0].tagKey) === "p";
187
+ const displayProject = first
188
+ ? formatProjectDisplay(first.projectKey, personalRollup ? { personal: true } : undefined)
189
+ : pk;
149
190
  const day = children[0].day;
150
191
  const parentMinutes = children.reduce((s, c) => s + c.minutes, 0);
151
192
  blocks.push({
152
193
  kind: "rollup",
153
194
  projectKeyLower: pk,
195
+ rollupStableKey,
154
196
  displayProject,
155
197
  day,
156
198
  parentMinutes,
@@ -34,8 +34,8 @@ export type SettingsCopy = {
34
34
  sectionPrivacy: string;
35
35
  /** Tableau de bord web : affichage du KronoFocus. */
36
36
  sectionKronoFocus: string;
37
- kronoFocusShowInHeader: string;
38
- kronoFocusShowInHeaderDesc: string;
37
+ /** KronoFocus dans la barre d’outils : toujours affiché (navigation stable). */
38
+ kronoFocusHeaderPinnedNote: string;
39
39
  kronoFocusShowInTaskOps: string;
40
40
  kronoFocusShowInTaskOpsDesc: string;
41
41
  /** Comportement des tâches sans étiquette explicite (#). */
@@ -491,8 +491,8 @@ const en: SettingsCopy = {
491
491
  sectionGeneral: "General",
492
492
  sectionPrivacy: "Privacy",
493
493
  sectionKronoFocus: "KronoFocus (dashboard)",
494
- kronoFocusShowInHeader: "Show in the header",
495
- kronoFocusShowInHeaderDesc: "KronoFocus timer and controls in the top bar of the browser dashboard.",
494
+ kronoFocusHeaderPinnedNote:
495
+ "The KronoFocus timer and controls always stay in the app header toolbar (next to the clock and command palette) so the navigation bar keeps the same width on every page. While you inspect an archive, the controls stay visible but target the live session.",
496
496
  kronoFocusShowInTaskOps: "Show in task action buttons",
497
497
  kronoFocusShowInTaskOpsDesc:
498
498
  "KronoFocus start on active tasks and the optional “start KronoFocus with new task” toggle in the task launcher.",
@@ -975,9 +975,8 @@ const fr: SettingsCopy = {
975
975
  sectionGeneral: "Général",
976
976
  sectionPrivacy: "Confidentialité",
977
977
  sectionKronoFocus: "KronoFocus (tableau de bord)",
978
- kronoFocusShowInHeader: "Afficher dans l’en-tête",
979
- kronoFocusShowInHeaderDesc:
980
- "Minuteur KronoFocus et contrôles dans la barre du haut du tableau de bord dans le navigateur.",
978
+ kronoFocusHeaderPinnedNote:
979
+ "Le minuteur KronoFocus et ses contrôles restent toujours dans la barre d’outils de l’application (à côté de l’horloge et de la palette de commandes), afin que la barre de navigation garde la même largeur sur toutes les pages. En consultation d’archive, les contrôles restent visibles mais ciblent la session live.",
981
980
  kronoFocusShowInTaskOps: "Afficher dans les boutons opérationnels des tâches",
982
981
  kronoFocusShowInTaskOpsDesc:
983
982
  "Bouton pour lancer le KronoFocus depuis une tâche suivie et option « lancer aussi le KronoFocus » lors de la création d’une tâche.",
@@ -50,7 +50,7 @@ function frBundle(): UserGuideBundle {
50
50
  searchNoResults: "Aucune rubrique ne correspond à votre recherche. Essayez un autre mot-clé ou effacez le filtre.",
51
51
  tocHeading: "Sur cette page",
52
52
  tocNavAria: "Sommaire du guide d’utilisation",
53
- lastUpdated: "Dernière mise à jour : 12 mai 2026",
53
+ lastUpdated: "Dernière mise à jour : 13 mai 2026",
54
54
  sections: [
55
55
  {
56
56
  id: "ug-essence",
@@ -64,7 +64,7 @@ function frBundle(): UserGuideBundle {
64
64
  optionsTitle: "Trois « pièces » pour trois gestes",
65
65
  options: [
66
66
  "**Tableau de bord** — l’*atelier* (sessions, tâches, minuteur, raccourcis **#étiquettes** / **@projets** travail / **!projets** personnels).",
67
- "**Rapports** — le *regard en hauteur* (période, thèmes, graphiques, sans refabriquer les chiffres).",
67
+ "**Rapports** — la période en **trois vues** (allocation facturation, rythme, avancé) à partir de l’historique déjà enregistré.",
68
68
  "**Paramètres** — l’*arrière-boutique* (habitudes, raccordements, zone où l’on *efface* l’histoire *avec prudence*). [Ouvrir Paramètres](/settings#settings-usage-profile).",
69
69
  ],
70
70
  stepsTitle: "Premier contact en cinq pas (sans tout maîtriser le jour 1)",
@@ -79,9 +79,10 @@ function frBundle(): UserGuideBundle {
79
79
  {
80
80
  id: "ug-nav-theme",
81
81
  title: "Barre d’outils, thème et langue",
82
- searchIndex: "thème clair sombre mode violet zinc langue fr en",
82
+ searchIndex: "thème clair sombre mode violet zinc langue fr en ruban groupe navigation repli plusieurs rangées",
83
83
  paragraphs: [
84
- "La **barre du haut** est *toujours* la même famille : mêmes outils, même **ton** (zinc, accents **violet**), pour ne pas recharger le cerveau *à chaque page*. L’icône **roue** mène à [Paramètres](/settings#settings-usage-profile) (raccourci **Alt+Shift+S** depuis le tableau de bord).",
84
+ "La **barre du haut** est *toujours* la même famille : mêmes outils, même **ton** (zinc, accents **violet**), pour ne pas recharger le cerveau *à chaque page*. Les **raccourcis** de pages sont **regroupés** en **petits blocs** par **contexte** (zones principales, aide et documentation, outils de session ou informations légales), sur le modèle d’un **ruban** léger. L’icône **roue** mène à [Paramètres](/settings#settings-usage-profile) (raccourci **Alt+Shift+S** depuis le tableau de bord).",
85
+ "Sur **écran étroit** (petit portable, fenêtre réduite, **PWA** côté tablette), la rangée se **replie** en **plusieurs lignes centrées** au lieu d’afficher une **barre de défilement horizontale** — les **blocs** (horloge, KronoFocus, navigation, utilitaires) restent **groupés** et accessibles **sans glisser** latéralement.",
85
86
  ],
86
87
  steps: [
87
88
  "En **tableau de bord** : l’icône **Livre** mène ici, puis **Rapports** (graphe), **Paramètres** (roue) et **Implémentation** (code — inventaire des user stories).",
@@ -106,10 +107,10 @@ function frBundle(): UserGuideBundle {
106
107
  ],
107
108
  options: [
108
109
  "**Tableau de bord** — à gauche la **file des sessions** ; au centre les **tâches** (minuteurs, détail) ; à droite les raccourcis d’**étiquettes** et de **projets** (**@** travail, **!** personnel) pour ne pas retaper les mêmes mots. C’est l’usage **quotidien**.",
109
- "**Rapports** — choisir une **plage de dates** et, si besoin, des **#étiquettes** ; lire résumés et graphiques. Une **visite guidée** est proposée la première fois sur l’écran.",
110
+ "**Rapports** — choisir une **plage de dates** et, si besoin, des **#étiquettes** ; puis les **onglets** (allocation facturation, rythme, avancé). Une **visite guidée** est proposée la première fois sur l’écran.",
110
111
  "**Paramètres** — profil, collecte, planification, étiquettes avancées, **Git** ou miroir en **option** ; la **zone dangereuse** supprime l’historique (avec saisie de confirmation). [Sommaire des paramètres](/settings#settings-usage-profile) · [zone de danger](/settings#settings-danger-zone). N’y entrez **que** quand vous le **décidez**.",
111
112
  "**Licences** — textes MIT, logiciels tiers, polices ; icône **document** **uniquement** sur cette page.",
112
- "Une **horloge murale** (fuseau et format 12/24 h des paramètres **Tableau de bord web**) tourne en permanence dans la **barre d’outils** de l’en-tête, à gauche des icônes de navigation."
113
+ "Une **horloge murale** (fuseau et format 12/24 h des paramètres **Tableau de bord web**) tourne en permanence dans la **barre d’outils** de l’en-tête ; le **KronoFocus** se place **juste à côté**, avant les icônes de navigation — il reste **toujours** visible pour garder la barre stable. En consultation d’**archive**, ses **boutons** restent **visibles** mais **désactivés** : ils reflètent toujours la **session live**. La **palette données** (Ctrl+K / ⌘K), le **Gantt du jour** et la **pause globale** sont **les mêmes** sur **toutes** les pages (tableau de bord, rapports, paramètres, journaux, guide, implémentation, changelog, licences) ; hors tableau de bord, certaines actions **vous y ramènent** pour terminer (ex. ouvrir un résultat de recherche)."
113
114
  ],
114
115
  },
115
116
  {
@@ -178,7 +179,7 @@ function frBundle(): UserGuideBundle {
178
179
  ],
179
180
  optionsTitle: "Où c’est proposé, quoi cliquer",
180
181
  options: [
181
- "Section [KronoFocus (tableau de bord)](/settings#settings-kronoFocus) dans **Paramètres** : minuteur **dans len-tête** et/ou **près des actions de tâche** — cocher selon l’encombrement (le **profil** d’usage peut **masquer** d’autres blocs ailleurs).",
182
+ "Section [KronoFocus (tableau de bord)](/settings#settings-kronoFocus) dans **Paramètres** : le minuteur reste **dans la barre doutils** ; vous choisissez encore s’il apparaît **près des actions de tâche** (le **profil** d’usage peut **masquer** d’autres blocs ailleurs).",
182
183
  "Sur le **panneau** : **Démarrer**, **Pause**, reprise ; **durée** en `HH:MM:SS` ; **durée par défaut** (p. ex. 25 min) d’un clic ; **historique** des durées pour **réappliquer** la même mesure **sans** la retaper.",
183
184
  "Lier le KronoFocus **à** la tâche **active** : utile pour **retrouver** dans l’historique **quelle** tâche portait ce **bloc** de focus."
184
185
  ],
@@ -224,23 +225,24 @@ function frBundle(): UserGuideBundle {
224
225
  {
225
226
  id: "ug-reporting",
226
227
  title: "Rapports : la mémoire qui tient, quand on doit la raconter",
227
- searchIndex: "agregats kpi plage date étiquette semaine assiduité retard heure planifiée",
228
+ searchIndex: "agregats kpi plage date étiquette semaine assiduité retard heure planifiée onglets allocation facturation rythme avancé",
228
229
  paragraphs: [
229
230
  "Les **rapports** ne jaugent pas votre humanité : ils aident à **répondre** à *est-ce qu’on peut, ensemble, s’y retrouver sur une période ?* Ce qui n’est **pas** **encore** fini reste **indiqué** (brouillon **honnête**, pas punition).",
230
- "L’appli **agrège** ce que **vous** avez **déjà** posé (durées, tags, projets) ; parfois des **lignes** / **signaux** côté code et un **aperçu** de dossier **si** cest **activé** chez vous. Une **visite** sur l’écran adoucit le **premier** tour.",
231
- "Lorsque l’**hôte** d’enregistrement a **fourni** l’heure de **début prévue** à l’ouverture d’une session (p. ex. moteur lié à l’[horaire](/settings#settings-schedule) ou aux [sessions planifiées](/settings#settings-planned-sessions)), l’**écart** (retard ou avance) est **sauvegardé** sur la session et des **indicateurs d’assiduité** apparaissent dans le **résumé** des **Rapports** : ce n’est en général **pas** rempli par le seul bouton *Nouvelle session* du navigateur, sauf intégration explicite."
231
+ "Sous les filtres, **trois vues** orientent la lecture : **Allocation facturation** (#étiquettes, @projets) pour le temps à mapper vers vos lignes de facturation ; **Rythme** (graphiques par jour) ; **Avancé** (synthèse, tableau journalier, lignes de code / signaux et instantané workspace **si** le profil lexpose). L’appli **agrège** ce que vous avez **déjà** enregistré ; une **visite** sur l’écran adoucit le **premier** tour.",
232
+ "Lorsque l’**hôte** d’enregistrement a **fourni** l’heure de **début prévue** à l’ouverture d’une session (p. ex. moteur lié à l’[horaire](/settings#settings-schedule) ou aux [sessions planifiées](/settings#settings-planned-sessions)), l’**écart** (retard ou avance) est **sauvegardé** sur la session et des **indicateurs d’assiduité** apparaissent dans le **résumé** (vue **Avancé**) : ce n’est en général **pas** rempli par le seul bouton *Nouvelle session* du navigateur, sauf intégration explicite."
232
233
  ],
233
234
  stepsTitle: "Parcours type sur l’écran Rapports",
234
235
  steps: [
235
236
  "Ouvrir **Rapports** (icône **graphe** ou raccourci **Alt+Shift+G** sur le tableau de bord).",
236
237
  "Choisir une **plage** : champs **Du** / **Au**, bouton **Aujourd’hui**, ou préréglages **jour** / **semaine** / **mois** / **année** (selon ce que l’UI expose).",
237
238
  "Optionnel : **sélectionner** une ou plusieurs **#étiquettes** pour **restreindre** les tâches (les **sessions** **sans** tâche **correspondante** **disparaissent** des **vues** **basées** **tâches**).",
238
- "Lire les **blocs** : **résumé**, **tableaux** par **jour**, temps par **@projet** / **!projet** / **#tag** (les grilles **par projet** du tableau **Rapports** comptent par défaut le temps **@** sans y mélanger le temps **!**) ; **navigation** de **semaine** **le** **cas** **échéant** ; **lancer** la **visite** **guidée** **si** la **pancarte** **le** **propose**.",
239
+ "Choisir une **vue** sous les filtres : **Allocation facturation** pour le temps par #étiquette et @projet (grilles hebdo, navigation de semaine, début de semaine) ; **Rythme** pour les graphiques par jour ; **Avancé** pour les indicateurs de synthèse, le tableau journalier, les lignes de code (si activées) et l’instantané workspace.",
240
+ "**Lancer** la **visite** **guidée** **si** la **pancarte** **le** **propose**.",
239
241
  ],
240
242
  options: [
241
243
  "**Indicateurs non finaux** (pastilles) : rappellent le **travail encore ouvert** dans la plage — lire prudemment, pas de chiffrage *figé* tant que des tâches *bougent* encore.",
242
- "Sous le **résumé**, une **ventilation** peut compter les **sessions** par **type de clôture** enregistré à la fin de session (lorsque l’hôte en a choisi un) ; les sessions **sans** catégorie y figurent à part.",
243
- "Le **tableau par jour** inclut aussi un total de temps tâche **non concurrent** (sans chevauchement des intervalles horodatés) pour comparer avec le temps enregistré brut.",
244
+ "Sous le **résumé** (vue **Avancé**), une **ventilation** peut compter les **sessions** par **type de clôture** enregistré à la fin de session (lorsque l’hôte en a choisi un) ; les sessions **sans** catégorie y figurent à part.",
245
+ "Le **tableau par jour** (vue **Avancé**) inclut aussi un total de temps tâche **non concurrent** (sans chevauchement des intervalles horodatés) pour comparer avec le temps enregistré brut.",
244
246
  "**Début de semaine** (lundi, dimanche ou samedi) : réglage souvent près des **calendriers** hebdo des rapports — choisissez **ce** qui coïncide avec **votre** semaine réelle.",
245
247
  "Relancer la visite **Rapports** (aide **premier pas** sur l’écran) : [Paramètres — visite Rapports](/settings#settings-reporting-tour).",
246
248
  "Une **deuxième grille** dédiée au seul temps personnel (`!`), **miroir** de celle des projets `@`, **n’est pas** encore dans l’interface ; les agrégats côté moteur prévoient déjà les filtres pour une version ultérieure.",
@@ -286,7 +288,7 @@ function enBundle(): UserGuideBundle {
286
288
  searchNoResults: "No section matches. Try another keyword or clear the filter.",
287
289
  tocHeading: "On this page",
288
290
  tocNavAria: "User guide table of contents",
289
- lastUpdated: "Last updated: 12 May 2026",
291
+ lastUpdated: "Last updated: 13 May 2026",
290
292
  sections: [
291
293
  {
292
294
  id: "ug-essence",
@@ -300,7 +302,7 @@ function enBundle(): UserGuideBundle {
300
302
  optionsTitle: "Three “rooms” for three moves",
301
303
  options: [
302
304
  "**Dashboard** — the workbench (sessions, tasks, focus timer, **#** tags, **@** work projects, and **!** personal projects).",
303
- "**Reports** — the look back (range, themes, charts: numbers come from what you **already** logged).",
305
+ "**Reports** — the span in **three tabs** (billing allocation, rhythm, advanced) from what you **already** logged.",
304
306
  "**Settings** — the back room (habits, connections, the **danger** zone where you wipe history **carefully**). [Open Settings](/settings#settings-usage-profile).",
305
307
  ],
306
308
  stepsTitle: "A calm first run (day one need not be perfect)",
@@ -315,9 +317,10 @@ function enBundle(): UserGuideBundle {
315
317
  {
316
318
  id: "ug-nav-theme",
317
319
  title: "Toolbar, theme, and language",
318
- searchIndex: "theme light dark language fr en brand",
320
+ searchIndex: "theme light dark language fr en ribbon group navigation wrap multiple rows narrow screen",
319
321
  paragraphs: [
320
- "The top bar reuses the **same** **family** of controls (zinc, **violet** accents) on every page so **navigation** stays **light** on the brain. The **cog** icon opens [Settings](/settings#settings-usage-profile) (shortcut **Alt+Shift+S** from the dashboard).",
322
+ "The top bar reuses the **same** **family** of controls (zinc, **violet** accents) on every page so **navigation** stays **light** on the brain. **Page shortcuts** sit in **small grouped blocks** by **context** (main areas, help and docs, in-session tools or legal info when shown), like a **lightweight ribbon**. The **cog** icon opens [Settings](/settings#settings-usage-profile) (shortcut **Alt+Shift+S** from the dashboard).",
323
+ "On a **narrow screen** (small laptop, shrunk window, tablet **PWA**), the row **wraps** into **multiple centered lines** instead of showing a **horizontal scrollbar** — the **blocks** (clock, KronoFocus, navigation, utilities) stay **grouped** and reachable **without side-scrolling**.",
321
324
  ],
322
325
  steps: [
323
326
  "On the **dashboard**: **Book** → this guide; **chart** → **Reports**; **cog** → **Settings**; **code** → user stories (implementation status).",
@@ -342,10 +345,10 @@ function enBundle(): UserGuideBundle {
342
345
  ],
343
346
  options: [
344
347
  "**Dashboard** — **sessions** on the left, **tasks** in the **centre**, **tag** and **project** shortcuts (**@** work, **!** personal) on the right so you stop retyping the same words.",
345
- "**Reports** — pick a **date range** and, if needed, **#** tags; read **summaries** and **charts**; the **tour** may offer itself the first time you land there.",
348
+ "**Reports** — pick a **date range** and, if needed, **#** tags; then use the **tabs** (billing allocation, rhythm, advanced); the **tour** may offer itself the first time you land there.",
346
349
  "**Settings** — profile, collection, scheduling, **advanced** tags, optional **Git** or **remote**; the **danger zone** can **delete** all **history** (type-to-confirm). [Settings overview](/settings#settings-usage-profile) · [danger zone](/settings#settings-danger-zone).",
347
350
  "**Licenses** — MIT, third parties, **fonts**; the **file** icon **only** on that page.",
348
- "A **wall clock** (time zone and 12/24 h from **Web dashboard** settings) stays visible in the header **toolbar**, left of the navigation icons."
351
+ "A **wall clock** (time zone and 12/24 h from **Web dashboard** settings) stays visible in the header **toolbar**; **KronoFocus** sits **right beside it**, before the navigation icons — it is **always** shown so the bar stays stable. While you **inspect an archive**, its **buttons** stay **visible but disabled** — they still reflect the **live session**. The **data palette** (Ctrl+K / ⌘K), **Today Gantt**, and **global pause** are the **same** on **every** main page (dashboard, reporting, settings, logs, guide, implementation, changelog, licenses); off the **dashboard**, some actions **return** there to **finish** (e.g. open a search hit)."
349
352
  ],
350
353
  },
351
354
  {
@@ -413,7 +416,7 @@ function enBundle(): UserGuideBundle {
413
416
  ],
414
417
  optionsTitle: "Where to find it, what the controls do",
415
418
  options: [
416
- "In **Settings**: the [KronoFocus (dashboard)](/settings#settings-kronoFocus) section — show the timer in the **header** and/or **on** the **task** (your **usage** profile can hide other panels elsewhere in the app).",
419
+ "In **Settings**: the [KronoFocus (dashboard)](/settings#settings-kronoFocus) section — the timer **stays in the header**; you can still choose whether it also appears **on** **task** actions (your **usage** profile can hide other panels elsewhere in the app).",
417
420
  "On the **card**: **Start**, **Pause**, resume; **duration** as `HH:MM:SS`; a **one-tap** default (e.g. 25 min); **history** of durations to **reapply** without retyping.",
418
421
  "Link the timer to the **active** **task** so you can later see **which** **task** carried that focus block in the **archive**.",
419
422
  ],
@@ -459,11 +462,11 @@ function enBundle(): UserGuideBundle {
459
462
  {
460
463
  id: "ug-reporting",
461
464
  title: "Reports: memory you can read back, not a verdict on you",
462
- searchIndex: "filter date tag week kpi punctuality late schedule planned",
465
+ searchIndex: "filter date tag week kpi punctuality late schedule planned tabs billing rhythm advanced",
463
466
  paragraphs: [
464
467
  "Reports are for a **calm** read of a time span, not a moral tally. They help answer: can we, together, make sense of this **period**? Unfinished work stays **indicated** where the UI does — honest draft, not **punishment**.",
465
- "The app **aggregates** what you **already** recorded (durations, tags, projects), sometimes with code-related lines and a **folder** peek if that is **on** in your **setup**; a **guided** **tour** may soften the **first** visit.",
466
- "If the **recording** **host** **passed** a **planned** start instant when a session was opened (for example a worker tied to your [work schedule](/settings#settings-schedule) or [planned sessions](/settings#settings-planned-sessions)), the app stores the **offset** in minutes and shows **punctuality** **summary** KPIs on **Reporting** — a browser-only **New session** usually does **not** set this by itself, unless a connector does.",
468
+ "Under the filters, **three tabs** steer the page: **Billing allocation** (#tags, @projects) for time you map to billing lines; **Rhythm** (per-day charts); **Advanced** (summary KPIs, day table, code lines / signals, and a **workspace** snapshot when your profile exposes it). The app **aggregates** what you **already** logged; a **guided** **tour** may soften the **first** visit.",
469
+ "If the **recording** **host** **passed** a **planned** start instant when a session was opened (for example a worker tied to your [work schedule](/settings#settings-schedule) or [planned sessions](/settings#settings-planned-sessions)), the app stores the **offset** in minutes and shows **punctuality** **summary** KPIs under **Advanced** — a browser-only **New session** usually does **not** set this by itself, unless a connector does.",
467
470
  ],
468
471
  stepsTitle: "A typical run on the Reports page",
469
472
  steps: [
@@ -471,13 +474,13 @@ function enBundle(): UserGuideBundle {
471
474
  "Set **from** and **to** (or a day / week / month / year **preset**, depending on what the UI offers).",
472
475
  "Use **Today** / **Now** quick actions in date/time pickers whenever available to jump to the current value.",
473
476
  "Optionally pick one or more **#** **tags** (sessions with **no** **matching** task may **disappear** from some **task**-based **views**).",
474
- "Scroll the **summary**, per-day **tables**, **@** / **!** / **#** **breakdowns** (the **per-project** grids on **Reports** default to **@** work time and **exclude** **!** personal minutes from those rows), and the week nudger when your data has **enough** **weeks** to flip through.",
477
+ "Pick a **tab** under the filters: **Billing allocation** for time by #tag and @project (weekly grids, week nudger, week start); **Rhythm** for per-day charts; **Advanced** for summary KPIs, the day table, lines-of-code (when on), and the workspace snapshot.",
475
478
  "Start the **guided** **tour** if a prompt appears the **first** time.",
476
479
  ],
477
480
  options: [
478
481
  "**Non-final** chips mark work that is not **closed** in the **range** you are looking at: read them gently; the **draft** is not a **failure**.",
479
- "Below the **summary** KPIs, a **breakdown** may count **sessions** by **closure type** saved when a session ended (when the host picked one); sessions **without** a stored category are grouped separately.",
480
- "The **day table** now includes a **non-concurrent** task-time total (timestamped overlap removed) so you can compare it against raw recorded task time.",
482
+ "Under **Advanced**, below the **summary** KPIs, a **breakdown** may count **sessions** by **closure type** saved when a session ended (when the host picked one); sessions **without** a stored category are grouped separately.",
483
+ "The **day table** (under **Advanced**) includes a **non-concurrent** task-time total (timestamped overlap removed) so you can compare it against raw recorded task time.",
481
484
  "**Week** starts on (Monday, Sunday, or Saturday) — often next to the tag-week **grids**; pick the one that **matches** how you read a week.",
482
485
  "Re-launch the **Reports** first-time **tour** from [Settings — Reporting tour](/settings#settings-reporting-tour).",
483
486
  "A **second** grid that shows **only** personal (`!`) project time, **parallel** to the `@` project grid, is **not** in the UI yet; the aggregation layer already has filters for a future release.",