@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.
- package/README.md +1 -1
- package/app/changelog/page.tsx +87 -19
- package/app/globals.css +10 -8
- package/app/guide/page.tsx +71 -34
- package/app/implementation/page.tsx +70 -60
- package/app/licenses/page.tsx +79 -47
- package/app/logs/page.tsx +103 -47
- package/app/page.tsx +104 -169
- package/app/reporting/page.tsx +1918 -1436
- package/app/settings/page.tsx +66 -44
- package/components/KronosysPayloadProvider.tsx +19 -5
- package/components/dashboard/AppShellHeaderKronoFocus.tsx +78 -0
- package/components/dashboard/AppShellHeaderToolbarLayout.tsx +36 -0
- package/components/dashboard/AppShellHeaderUtilityRibbon.tsx +19 -0
- package/components/dashboard/AppShellHeaderWallClock.tsx +23 -17
- package/components/dashboard/AppShellRouteNav.tsx +336 -209
- package/components/dashboard/AppShellToolbarCommandCenter.tsx +225 -0
- package/components/dashboard/AppShellToolbarRouteNav.tsx +204 -0
- package/components/dashboard/DashboardCommandCenter.tsx +119 -30
- package/components/dashboard/KronoFocusPanel.tsx +287 -260
- package/components/dashboard/LanguageMenu.tsx +23 -7
- package/components/dashboard/PageRefreshButton.tsx +42 -16
- package/components/dashboard/ReportingTour.tsx +20 -2
- package/components/dashboard/SessionListPanel.tsx +4 -4
- package/components/dashboard/ThemeToggle.tsx +4 -3
- package/components/dashboard/useAnchoredFloatingPortalStyle.ts +9 -2
- package/components/dashboard/useKronoFocusLiveSeconds.ts +4 -2
- package/lib/appShellHeaderClasses.ts +22 -3
- package/lib/appShellToolbarChrome.ts +112 -0
- package/lib/appShellToolbarDeferredIntents.ts +112 -0
- package/lib/appShellToolbarSessionSlices.ts +67 -0
- package/lib/dashboardCopy.ts +78 -29
- package/lib/dashboardQuickSearch.ts +37 -6
- package/lib/dashboardUrlSession.ts +36 -0
- package/lib/generatedUserChangelog.ts +14 -0
- package/lib/implementationNotes.ts +18 -14
- package/lib/reportingAggregate.ts +68 -9
- package/lib/reportingMetricHelp.ts +8 -8
- package/lib/reportingStrings.ts +118 -9
- package/lib/reportingTagWeekBreakdown.ts +55 -13
- package/lib/settingsCopy.ts +6 -7
- package/lib/userGuideCopy.ts +29 -26
- package/package.json +7 -5
- package/server/db.ts +6 -4
- package/server/dbSchema.ts +2 -2
- package/components/dashboard/AppShellCommandCenterPlaceholder.tsx +0 -17
package/lib/dashboardCopy.ts
CHANGED
|
@@ -9,13 +9,8 @@ export type DashboardStrings = {
|
|
|
9
9
|
kronoFocusStart: string;
|
|
10
10
|
kronoFocusPause: string;
|
|
11
11
|
kronoFocusReset: string;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
/** Deuxième paragraphe du même popover ; vide = ignoré. */
|
|
15
|
-
kronoFocusAutoRefreshNote: string;
|
|
16
|
-
/** aria-label du bouton d’aide à côté du titre KronoFocus. */
|
|
17
|
-
kronoFocusPanelHelpAriaLabel: string;
|
|
18
|
-
kronoFocusLiveWhileViewingArchive: string;
|
|
12
|
+
/** Infobulle des boutons KronoFocus désactivés pendant la consultation d’une archive. */
|
|
13
|
+
kronoFocusControlsReadOnlyTooltip: string;
|
|
19
14
|
/** Ligne sous KronoFocus quand il a été lancé depuis une tâche (avant le nom cliquable) */
|
|
20
15
|
kronoFocusLinkedTaskIntro: string;
|
|
21
16
|
kronoFocusEditDurationTitle: string;
|
|
@@ -712,8 +707,10 @@ export type DashboardStrings = {
|
|
|
712
707
|
reportingTourStep3Body: string;
|
|
713
708
|
reportingTourStep4Title: string;
|
|
714
709
|
reportingTourStep4Body: string;
|
|
715
|
-
|
|
716
|
-
|
|
710
|
+
reportingTourStep5aTitle: string;
|
|
711
|
+
reportingTourStep5aBody: string;
|
|
712
|
+
reportingTourStep5bTitle: string;
|
|
713
|
+
reportingTourStep5bBody: string;
|
|
717
714
|
reportingTourStep6Title: string;
|
|
718
715
|
reportingTourStep6Body: string;
|
|
719
716
|
/** Modale « nouvelle session » — titre. */
|
|
@@ -766,6 +763,28 @@ export type DashboardStrings = {
|
|
|
766
763
|
appShellRouteNavGlobalResume: string;
|
|
767
764
|
/** Infobulle lorsque la pause globale est inactive (rien ne tourne). */
|
|
768
765
|
appShellRouteNavGlobalPauseDisabledTooltip: string;
|
|
766
|
+
/** Infobulle : Gantt du jour uniquement depuis le tableau de bord. */
|
|
767
|
+
appShellRouteNavGanttDashboardOnlyTooltip: string;
|
|
768
|
+
/** Infobulle : pause globale uniquement depuis le tableau de bord (session live). */
|
|
769
|
+
appShellRouteNavGlobalPauseDashboardOnlyTooltip: string;
|
|
770
|
+
/** Infobulle : pause globale indisponible sur le tableau de bord (archive consultée ou chargement). */
|
|
771
|
+
appShellRouteNavGlobalPauseNoSessionContextTooltip: string;
|
|
772
|
+
/** Infobulle : entrée changelog masquée sur cette route (libellé vide). */
|
|
773
|
+
appShellRouteNavChangelogUnavailableTooltip: string;
|
|
774
|
+
/** Infobulle : entrée implémentation masquée sur cette route (libellé vide). */
|
|
775
|
+
appShellRouteNavImplementationUnavailableTooltip: string;
|
|
776
|
+
/** `aria-label` du bandeau KronoFocus pendant l’absence de charge utile session. */
|
|
777
|
+
appShellHeaderKronoFocusLoadingAria: string;
|
|
778
|
+
/** `aria-label` du groupe de navigation : tableau de bord, rapports, paramètres, journaux. */
|
|
779
|
+
appShellRouteNavGroupAppAria: string;
|
|
780
|
+
/** `aria-label` du groupe : guide, détails d’implémentation. */
|
|
781
|
+
appShellRouteNavGroupDocsAria: string;
|
|
782
|
+
/** `aria-label` du groupe : Gantt du jour, pause globale (ou emplacement réservé). */
|
|
783
|
+
appShellRouteNavGroupToolsAria: string;
|
|
784
|
+
/** `aria-label` du groupe : page licences (lecture seule). */
|
|
785
|
+
appShellRouteNavGroupLegalAria: string;
|
|
786
|
+
/** `aria-label` du ruban : thème, rafraîchissement, langue. */
|
|
787
|
+
appShellUtilityToolbarGroupAria: string;
|
|
769
788
|
/** Modale — titre avant d’activer la pause globale. */
|
|
770
789
|
globalPauseConfirmTitle: string;
|
|
771
790
|
/** Modale — texte d’introduction (comportement + liste à suivre). */
|
|
@@ -863,12 +882,8 @@ const en: DashboardStrings = {
|
|
|
863
882
|
kronoFocusStart: "Start",
|
|
864
883
|
kronoFocusPause: "Pause",
|
|
865
884
|
kronoFocusReset: "Reset",
|
|
866
|
-
|
|
867
|
-
"
|
|
868
|
-
kronoFocusAutoRefreshNote: "",
|
|
869
|
-
kronoFocusPanelHelpAriaLabel: "KronoFocus panel help",
|
|
870
|
-
kronoFocusLiveWhileViewingArchive:
|
|
871
|
-
"While you browse an archived session, this still shows KronoFocus for the active live session (if any).",
|
|
885
|
+
kronoFocusControlsReadOnlyTooltip:
|
|
886
|
+
"Read-only while you inspect an archive — these controls target the live session.",
|
|
872
887
|
kronoFocusLinkedTaskIntro: "Started from:",
|
|
873
888
|
kronoFocusEditDurationTitle: "Configure KronoFocus rhythm (timer not started)",
|
|
874
889
|
kronoFocusDurationPickerLabel: "Duration (hours : minutes : seconds)",
|
|
@@ -1384,7 +1399,7 @@ const en: DashboardStrings = {
|
|
|
1384
1399
|
"This area shows your Git author line and forge account when they are configured in Settings → Git identity, the workspace roots detected for metrics, and a small badge for how session data is stored here: SQLite (default), JSON driver, or MongoDB mirror when enabled.\n\nSQLite keeps the dashboard payload on this machine under your Kronosys data directory; this page reads and writes it through the local API.",
|
|
1385
1400
|
tourStep8Title: "What is KronoFocus?",
|
|
1386
1401
|
tourStep8Body:
|
|
1387
|
-
"**KronoFocus** paces your session with a configurable work segment, short breaks, then a long break after several cycles. It is built into Kronosys (counts, phases, how it ties to the session) and is not limited to a fixed Pomodoro-style recipe.\n\
|
|
1402
|
+
"**KronoFocus** paces your session with a configurable work segment, short breaks, then a long break after several cycles. It is built into Kronosys (counts, phases, how it ties to the session) and is not limited to a fixed Pomodoro-style recipe.\n\nThe header toolbar always includes KronoFocus so navigation stays aligned on every page. Whether you also show it next to task actions is configured under Settings → Dashboard & API (KronoFocus).",
|
|
1388
1403
|
tourStep8LearnMoreUrl: "https://en.wikipedia.org/wiki/Time_management",
|
|
1389
1404
|
tourStep8LearnMoreLabel: "Further reading — time management (new tab)",
|
|
1390
1405
|
tourStep8LearnMoreAriaLabel: "Opens the English Wikipedia article on time management in a new tab",
|
|
@@ -1432,9 +1447,12 @@ const en: DashboardStrings = {
|
|
|
1432
1447
|
reportingTourStep4Title: "Daily charts",
|
|
1433
1448
|
reportingTourStep4Body:
|
|
1434
1449
|
"Bar views show sessions, tasks by status, recorded task time, and session wall-clock by calendar day. When several weeks exist, use the week strip to align charts and tag calendars.",
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
"Review recorded task minutes grouped by #tags (including the untagged bucket when it applies)
|
|
1450
|
+
reportingTourStep5aTitle: "Time by tag",
|
|
1451
|
+
reportingTourStep5aBody:
|
|
1452
|
+
"Review recorded task minutes grouped by #tags (including the untagged bucket when it applies), with weekly grids when data spans multiple days.",
|
|
1453
|
+
reportingTourStep5bTitle: "Time by project (Work vs Personal)",
|
|
1454
|
+
reportingTourStep5bBody:
|
|
1455
|
+
"The weekly-by-project grid has a Work (@) / Personal (!) toggle under the section title: Work (default) excludes personal-ledger minutes; Personal shows only that ledger (including tasks inferred from a ! token in the title when the saved flag is missing). Tag totals above still mix both ledgers unless you filter by tags.",
|
|
1438
1456
|
reportingTourStep6Title: "Outline and two-column layout",
|
|
1439
1457
|
reportingTourStep6Body:
|
|
1440
1458
|
"This block is the main Reporting layout: your content on the left and, on wide screens, a sticky jump list on the right. On phones, the same anchors appear as chips above the filters.",
|
|
@@ -1483,6 +1501,22 @@ const en: DashboardStrings = {
|
|
|
1483
1501
|
appShellRouteNavGlobalResume: "Resume everything paused by global pause in this context",
|
|
1484
1502
|
appShellRouteNavGlobalPauseDisabledTooltip:
|
|
1485
1503
|
"Nothing is active to pause: the session wall clock is already paused and no task or subtask timer is running.",
|
|
1504
|
+
appShellRouteNavGanttDashboardOnlyTooltip:
|
|
1505
|
+
"Today’s Gantt is available from the dashboard only.",
|
|
1506
|
+
appShellRouteNavGlobalPauseDashboardOnlyTooltip:
|
|
1507
|
+
"Global pause is available from the dashboard when a live session is loaded.",
|
|
1508
|
+
appShellRouteNavGlobalPauseNoSessionContextTooltip:
|
|
1509
|
+
"Global pause is not available while you inspect an archived session or before the live session is loaded.",
|
|
1510
|
+
appShellRouteNavChangelogUnavailableTooltip:
|
|
1511
|
+
"Changelog shortcut is hidden on this screen (no label).",
|
|
1512
|
+
appShellRouteNavImplementationUnavailableTooltip:
|
|
1513
|
+
"Implementation shortcut is hidden on this screen (no label).",
|
|
1514
|
+
appShellHeaderKronoFocusLoadingAria: "KronoFocus — waiting for session data",
|
|
1515
|
+
appShellRouteNavGroupAppAria: "Main app areas",
|
|
1516
|
+
appShellRouteNavGroupDocsAria: "Help and reference",
|
|
1517
|
+
appShellRouteNavGroupToolsAria: "In-session tools",
|
|
1518
|
+
appShellRouteNavGroupLegalAria: "Legal information",
|
|
1519
|
+
appShellUtilityToolbarGroupAria: "Display and language",
|
|
1486
1520
|
globalPauseConfirmTitle: "Global pause",
|
|
1487
1521
|
globalPauseConfirmIntro:
|
|
1488
1522
|
"Global pause freezes the session wall clock when it is running, pauses each task timer that is not already paused, and stops any active subtask timer (recording elapsed time). The next click on this control resumes only what was paused in this operation.\n\nReview what will be affected:",
|
|
@@ -1540,12 +1574,8 @@ const fr: DashboardStrings = {
|
|
|
1540
1574
|
kronoFocusStart: "Démarrer",
|
|
1541
1575
|
kronoFocusPause: "Pause",
|
|
1542
1576
|
kronoFocusReset: "Réinitialiser",
|
|
1543
|
-
|
|
1544
|
-
"
|
|
1545
|
-
kronoFocusAutoRefreshNote: "",
|
|
1546
|
-
kronoFocusPanelHelpAriaLabel: "Aide sur le panneau KronoFocus",
|
|
1547
|
-
kronoFocusLiveWhileViewingArchive:
|
|
1548
|
-
"Pendant la consultation d’une session archivée, ce panneau affiche encore KronoFocus pour la session en cours (s’il y en a une).",
|
|
1577
|
+
kronoFocusControlsReadOnlyTooltip:
|
|
1578
|
+
"Lecture seule pendant l’inspection d’une archive — ces commandes s’appliquent à la session en cours.",
|
|
1549
1579
|
kronoFocusLinkedTaskIntro: "Lancé depuis :",
|
|
1550
1580
|
kronoFocusEditDurationTitle: "Configurer le rythme KronoFocus (minuteur non démarré)",
|
|
1551
1581
|
kronoFocusDurationPickerLabel: "Durée (heures : minutes : secondes)",
|
|
@@ -2077,7 +2107,7 @@ const fr: DashboardStrings = {
|
|
|
2077
2107
|
"Ici s’affichent la ligne d’auteur Git et le compte forge lorsqu’ils sont renseignés dans Paramètres → Identité Git, les racines de workspace détectées pour les métriques, et une pastille indiquant comment les sessions sont enregistrées ici : SQLite (par défaut), pilote JSON ou miroir MongoDB si activé.\n\nSQLite conserve la charge utile du tableau de bord sur cette machine dans le répertoire de données Kronosys ; cette page lit et écrit via l’API locale.",
|
|
2078
2108
|
tourStep8Title: "Qu’est-ce que KronoFocus ?",
|
|
2079
2109
|
tourStep8Body:
|
|
2080
|
-
"**KronoFocus** cadence votre session : un segment de travail dont vous choisissez la durée, des pauses courtes, puis une pause longue après plusieurs cycles. Ce dispositif est intégré à Kronosys (compteurs, phases, lien à la session) ; il ne se résume pas à une méthode Pomodoro figée.\n\
|
|
2110
|
+
"**KronoFocus** cadence votre session : un segment de travail dont vous choisissez la durée, des pauses courtes, puis une pause longue après plusieurs cycles. Ce dispositif est intégré à Kronosys (compteurs, phases, lien à la session) ; il ne se résume pas à une méthode Pomodoro figée.\n\nLa barre d’outils affiche toujours KronoFocus pour garder la navigation alignée sur toutes les pages. L’affichage à côté des actions de tâche se règle toujours dans Paramètres → Tableau de bord et API (section KronoFocus).",
|
|
2081
2111
|
tourStep8LearnMoreUrl: "https://fr.wikipedia.org/wiki/Gestion_du_temps",
|
|
2082
2112
|
tourStep8LearnMoreLabel: "Pour aller plus loin — gestion du temps (nouvel onglet)",
|
|
2083
2113
|
tourStep8LearnMoreAriaLabel: "Ouvre l’article Wikipédia sur la gestion du temps dans un nouvel onglet",
|
|
@@ -2125,9 +2155,12 @@ const fr: DashboardStrings = {
|
|
|
2125
2155
|
reportingTourStep4Title: "Graphiques par jour",
|
|
2126
2156
|
reportingTourStep4Body:
|
|
2127
2157
|
"Les barres montrent les sessions, les tâches par état, le temps enregistré sur les tâches et la durée murale des sessions par jour calendaire. Quand plusieurs semaines existent, la bande de navigation aligne graphiques et calendriers par étiquette.",
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
"Consultez les minutes enregistrées sur les tâches regroupées par #étiquettes (y compris le compartiment sans étiquette explicite le cas échéant)
|
|
2158
|
+
reportingTourStep5aTitle: "Temps par étiquette",
|
|
2159
|
+
reportingTourStep5aBody:
|
|
2160
|
+
"Consultez les minutes enregistrées sur les tâches regroupées par #étiquettes (y compris le compartiment sans étiquette explicite le cas échéant), avec des grilles hebdomadaires lorsque les données couvrent plusieurs jours.",
|
|
2161
|
+
reportingTourStep5bTitle: "Temps par projet (Travail vs Personnel)",
|
|
2162
|
+
reportingTourStep5bBody:
|
|
2163
|
+
"La grille hebdomadaire par projet propose un sélecteur Travail (@) / Personnel (!) sous le titre de la section : Travail (défaut) exclut le registre personnel ; Personnel n’affiche que celui-ci (y compris les tâches déduites d’un jeton « ! » dans le titre si le drapeau enregistré manque). Les totaux par étiquette au-dessus combinent toujours les deux registres, sauf filtre par étiquettes.",
|
|
2131
2164
|
reportingTourStep6Title: "Sommaire et mise en page à deux colonnes",
|
|
2132
2165
|
reportingTourStep6Body:
|
|
2133
2166
|
"Ce bloc correspond à la disposition principale de Rapports : le contenu à gauche et, sur grand écran, le sommaire fixe à droite. Sur téléphone, les mêmes ancres sont proposées en pastilles au-dessus des filtres.",
|
|
@@ -2178,6 +2211,22 @@ const fr: DashboardStrings = {
|
|
|
2178
2211
|
"Reprendre uniquement ce qui a été mis en pause globalement dans ce contexte",
|
|
2179
2212
|
appShellRouteNavGlobalPauseDisabledTooltip:
|
|
2180
2213
|
"Rien n’est actif à mettre en pause : l’horloge murale est déjà figée et aucun minuteur de tâche ou de sous-tâche ne tourne.",
|
|
2214
|
+
appShellRouteNavGanttDashboardOnlyTooltip:
|
|
2215
|
+
"Le Gantt du jour n’est disponible que depuis le tableau de bord.",
|
|
2216
|
+
appShellRouteNavGlobalPauseDashboardOnlyTooltip:
|
|
2217
|
+
"La pause globale n’est disponible que depuis le tableau de bord lorsqu’une session live est chargée.",
|
|
2218
|
+
appShellRouteNavGlobalPauseNoSessionContextTooltip:
|
|
2219
|
+
"La pause globale n’est pas disponible pendant la consultation d’une archive ou avant le chargement de la session live.",
|
|
2220
|
+
appShellRouteNavChangelogUnavailableTooltip:
|
|
2221
|
+
"Raccourci changelog masqué sur cet écran (libellé vide).",
|
|
2222
|
+
appShellRouteNavImplementationUnavailableTooltip:
|
|
2223
|
+
"Raccourci implémentation masqué sur cet écran (libellé vide).",
|
|
2224
|
+
appShellHeaderKronoFocusLoadingAria: "KronoFocus — attente des données de session",
|
|
2225
|
+
appShellRouteNavGroupAppAria: "Zones principales de l’application",
|
|
2226
|
+
appShellRouteNavGroupDocsAria: "Aide et documentation",
|
|
2227
|
+
appShellRouteNavGroupToolsAria: "Outils de session",
|
|
2228
|
+
appShellRouteNavGroupLegalAria: "Informations légales",
|
|
2229
|
+
appShellUtilityToolbarGroupAria: "Affichage et langue",
|
|
2181
2230
|
globalPauseConfirmTitle: "Pause globale",
|
|
2182
2231
|
globalPauseConfirmIntro:
|
|
2183
2232
|
"La pause globale fige l’horloge murale de la session si elle tourne encore, met en pause chaque minuteur de tâche qui ne l’est pas déjà et arrête tout minuteur de sous-tâche actif (en consolidant le temps écoulé). Le prochain clic sur ce contrôle ne reprend que ce qui a été figé dans cette opération.\n\nVoici ce qui sera concerné :",
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
normalizeTagKey,
|
|
13
13
|
parseProjectScopedTag,
|
|
14
14
|
parseTaskWithAutoTags,
|
|
15
|
+
resolvePersonalProjectForTaskUpdate,
|
|
15
16
|
taskTitleForDisplay,
|
|
16
17
|
} from "@/lib/taskParsing";
|
|
17
18
|
|
|
@@ -48,6 +49,7 @@ type TaskLike = {
|
|
|
48
49
|
name?: string;
|
|
49
50
|
tags?: unknown;
|
|
50
51
|
project?: unknown;
|
|
52
|
+
personalProject?: boolean;
|
|
51
53
|
isDone?: boolean;
|
|
52
54
|
manualTaskTimerPaused?: boolean;
|
|
53
55
|
/** ISO 8601 côté modèle tâche (début d’enregistrement / entrée passée). */
|
|
@@ -100,7 +102,8 @@ function dateHayFromFields(...fields: (string | null | undefined)[]): string {
|
|
|
100
102
|
function buildTaskSearchIndex(
|
|
101
103
|
rawName: string,
|
|
102
104
|
storedTags: unknown,
|
|
103
|
-
storedProject: unknown
|
|
105
|
+
storedProject: unknown,
|
|
106
|
+
personalProject?: boolean,
|
|
104
107
|
): { haystackFragment: string; metaLine: string } {
|
|
105
108
|
const storedTagArr = Array.isArray(storedTags)
|
|
106
109
|
? storedTags.filter((x): x is string => typeof x === "string")
|
|
@@ -111,9 +114,22 @@ function buildTaskSearchIndex(
|
|
|
111
114
|
(typeof storedProject === "string" && storedProject.trim() ? storedProject.trim() : undefined) ??
|
|
112
115
|
parsed.project;
|
|
113
116
|
|
|
117
|
+
const personalLedger =
|
|
118
|
+
personalProject === true || resolvePersonalProjectForTaskUpdate(rawName) === true;
|
|
119
|
+
|
|
114
120
|
const hayPieces: string[] = [];
|
|
115
121
|
if (projRaw) {
|
|
116
|
-
|
|
122
|
+
const pkey = normalizeProjectKey(projRaw);
|
|
123
|
+
hayPieces.push(
|
|
124
|
+
projRaw,
|
|
125
|
+
pkey,
|
|
126
|
+
formatProjectDisplay(projRaw, personalLedger ? { personal: true } : undefined),
|
|
127
|
+
);
|
|
128
|
+
if (personalLedger) {
|
|
129
|
+
hayPieces.push(`!${pkey}`, "personnel", "personal", "registre personnel", "personal ledger");
|
|
130
|
+
} else {
|
|
131
|
+
hayPieces.push(`@${pkey}`);
|
|
132
|
+
}
|
|
117
133
|
}
|
|
118
134
|
for (const tag of tagList) {
|
|
119
135
|
hayPieces.push(tag, normalizeTagKey(tag), formatTagDisplay(tag));
|
|
@@ -130,7 +146,7 @@ function buildTaskSearchIndex(
|
|
|
130
146
|
|
|
131
147
|
const metaParts: string[] = [];
|
|
132
148
|
if (projRaw) {
|
|
133
|
-
metaParts.push(formatProjectDisplay(projRaw));
|
|
149
|
+
metaParts.push(formatProjectDisplay(projRaw, personalLedger ? { personal: true } : undefined));
|
|
134
150
|
}
|
|
135
151
|
for (const tag of tagList) {
|
|
136
152
|
metaParts.push(formatTagDisplay(tag));
|
|
@@ -353,7 +369,12 @@ export function buildDashboardQuickSearchItems(opts: {
|
|
|
353
369
|
const seenMeta = new Set<string>();
|
|
354
370
|
for (const t of tasks) {
|
|
355
371
|
const rawName = typeof t.name === "string" ? t.name : "";
|
|
356
|
-
const idx = buildTaskSearchIndex(
|
|
372
|
+
const idx = buildTaskSearchIndex(
|
|
373
|
+
rawName,
|
|
374
|
+
t.tags,
|
|
375
|
+
t.project,
|
|
376
|
+
t.personalProject === true,
|
|
377
|
+
);
|
|
357
378
|
hayFrags.push(idx.haystackFragment);
|
|
358
379
|
if (idx.metaLine) {
|
|
359
380
|
for (const part of idx.metaLine.split(" · ")) {
|
|
@@ -438,7 +459,12 @@ export function buildDashboardQuickSearchItems(opts: {
|
|
|
438
459
|
}
|
|
439
460
|
const rawName = typeof t.name === "string" ? t.name : "";
|
|
440
461
|
const display = taskTitleForDisplay(rawName);
|
|
441
|
-
const idx = buildTaskSearchIndex(
|
|
462
|
+
const idx = buildTaskSearchIndex(
|
|
463
|
+
rawName,
|
|
464
|
+
t.tags,
|
|
465
|
+
t.project,
|
|
466
|
+
t.personalProject === true,
|
|
467
|
+
);
|
|
442
468
|
const titleKey = (display || id.slice(0, 8)).trim() || id.slice(0, 8);
|
|
443
469
|
const taskTimeHay = dateHayFromFields(
|
|
444
470
|
readOptionalIsoString(t.startTime),
|
|
@@ -502,7 +528,12 @@ export function buildDashboardQuickSearchItems(opts: {
|
|
|
502
528
|
continue;
|
|
503
529
|
}
|
|
504
530
|
const rawName = typeof tpl.name === "string" ? tpl.name : "";
|
|
505
|
-
const idx = buildTaskSearchIndex(
|
|
531
|
+
const idx = buildTaskSearchIndex(
|
|
532
|
+
rawName,
|
|
533
|
+
tpl.tags,
|
|
534
|
+
tpl.project,
|
|
535
|
+
tpl.personalProject === true,
|
|
536
|
+
);
|
|
506
537
|
const displayTitle =
|
|
507
538
|
taskTitleForDisplay(rawName).trim() || tid.slice(0, 8);
|
|
508
539
|
const hay = `${rawName} ${displayTitle} ${tid} ${draft} ${idx.haystackFragment} ${tmplHayExtra} ${dt.dataSearchKindTaskTemplate}`
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SessionListEntry } from "@/components/dashboard/SessionListPanel";
|
|
2
|
+
import type { KronosysUpdatePayload } from "@/lib/kronosysApi";
|
|
3
|
+
|
|
4
|
+
/** Minimal `payload.current` pour la résolution `?session=`. */
|
|
5
|
+
export type UrlSessionLiveShape = { sessionId?: string };
|
|
6
|
+
|
|
7
|
+
export type UrlSessionResolution =
|
|
8
|
+
| { mode: "none" }
|
|
9
|
+
| { mode: "loading" }
|
|
10
|
+
| { mode: "ok"; id: string }
|
|
11
|
+
| { mode: "invalid" };
|
|
12
|
+
|
|
13
|
+
export function resolveUrlSession(
|
|
14
|
+
urlParam: string | null,
|
|
15
|
+
payload: KronosysUpdatePayload | null,
|
|
16
|
+
live: UrlSessionLiveShape | undefined,
|
|
17
|
+
history: SessionListEntry[],
|
|
18
|
+
historyArchived: SessionListEntry[],
|
|
19
|
+
): UrlSessionResolution {
|
|
20
|
+
if (!urlParam) {
|
|
21
|
+
return { mode: "none" };
|
|
22
|
+
}
|
|
23
|
+
if (!payload) {
|
|
24
|
+
return { mode: "loading" };
|
|
25
|
+
}
|
|
26
|
+
const liveSid =
|
|
27
|
+
typeof live?.sessionId === "string" ? live.sessionId.trim() : "";
|
|
28
|
+
if (
|
|
29
|
+
(liveSid !== "" && urlParam === liveSid) ||
|
|
30
|
+
history.some((s) => s.sessionId === urlParam) ||
|
|
31
|
+
historyArchived.some((s) => s.sessionId === urlParam)
|
|
32
|
+
) {
|
|
33
|
+
return { mode: "ok", id: urlParam };
|
|
34
|
+
}
|
|
35
|
+
return { mode: "invalid" };
|
|
36
|
+
}
|
|
@@ -5,6 +5,20 @@ export type UserChangelogEntry = {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
export const USER_CHANGELOG_ENTRIES: UserChangelogEntry[] = [
|
|
8
|
+
{
|
|
9
|
+
"version": "1.0.0-beta.22",
|
|
10
|
+
"items": [
|
|
11
|
+
"**Plus de barre de défilement horizontale** sur la barre d’outils d’en-tête (`lib/appShellHeaderClasses.ts`) : les segments **horloge / KronoFocus / palette de commandes / navigation de routes / utilitaires (thème, actualiser, langue)** passent désormais **à la ligne** (`flex-wrap`) lorsqu’ils ne tiennent plus, en gardant chaque rangée **centrée**, au lieu de produire un ascenseur horizontal sur le conteneur. Le comportement est identique sur **toutes** les routes principales (tableau de bord, rapports, paramètres, journaux, guide, implémentation, changelog, licences).",
|
|
12
|
+
"**Bandeau « Données actualisées » du bouton actualiser** (`components/dashboard/PageRefreshButton.tsx`) : rendu via **portail** vers `document.body` avec **`position: fixed`** au lieu d’un positionnement absolu sous le bouton. Corrige l’apparition intermittente d’une **barre de défilement verticale** sur la page à chaque clic — l’élément absolu participait au débordement du `<body>` et faisait osciller le `scrollHeight` pendant les ~2,8 s d’affichage du libellé.",
|
|
13
|
+
"**Popover d’édition de durée KronoFocus** (`components/dashboard/KronoFocusPanel.tsx`) : ouvrir le temps depuis l’en-tête (`variant=\"headerBar\"`) ne déclenche plus l’apparition de **scrollbars sur les deux axes** dans l’alvéole du ruban. La cause était la combinaison `overflow-x-auto` sur l’alvéole + popover en `position: absolute` : selon la spec CSS, dès qu’un axe d’overflow n’est pas `visible`, l’autre est promu en `auto`, donc deux ascenseurs apparaissaient sitôt que le panneau dépassait. Le popover est désormais rendu en **portail** avec `position: fixed` centré sous le bouton du temps. Détection click-outside révisée pour vérifier le **déclencheur** et le **panneau** par leurs `ref` distinctes (ils ne partagent plus de sous-arbre DOM).",
|
|
14
|
+
"**Surcouche carte « complète »** (variante `default` de `KronoFocusPanel`) alignée sur le même pattern portail pour rester cohérente.",
|
|
15
|
+
"**`useAnchoredFloatingPortalStyle`** (`components/dashboard/useAnchoredFloatingPortalStyle.ts`) gagne l’alignement **`\"center\"`** en plus de `start` / `end` : le panneau portail peut maintenant être centré horizontalement sous son déclencheur. Les autres consommateurs (`PageRefreshButton`, `PlainHelpPopover`, `TagsHelpTrigger`, `InlineMetricHelpTrigger`) conservent leurs alignements existants ; le hook continue de gérer la contrainte au viewport (marge 10 px) et le **basculement au-dessus** du déclencheur s’il manque de place en bas.",
|
|
16
|
+
"**Guide utilisateur** : `lib/userGuideCopy.ts` et `docs/USER_GUIDE.md` (FR/EN) — note explicite sur le repli en plusieurs rangées de la barre d’outils sur écran étroit ; en-tête de version pointe **`1.0.0-beta.22`**.",
|
|
17
|
+
"**Implémentation** : `lib/implementationNotes.ts` (FR + EN, `lastUpdated`) ; `docs/IMPLEMENTATION_DETAILS.md` — date d’en-tête et note sur le pattern « portail + `position: fixed` » pour les surcouches ancrées à des éléments d’en-tête (évite la propagation au `scrollableOverflow` du `<body>`).",
|
|
18
|
+
"**Dépôt** : `README.md` (référence de version npm).",
|
|
19
|
+
"Bump applicatif : **`1.0.0-beta.22`** (`package.json`, `package-lock.json`) ; **CHANGELOG** usager régénéré (`npm run changelog:build`)."
|
|
20
|
+
]
|
|
21
|
+
},
|
|
8
22
|
{
|
|
9
23
|
"version": "1.0.0-beta.21",
|
|
10
24
|
"items": [
|
|
@@ -55,7 +55,7 @@ const frBundle: ImplementationNotesBundle = {
|
|
|
55
55
|
"Liste exhaustive des intentions utilisateur·rice·s : chaque ligne indique si la capacité est livrée (✓) ou absente / hors périmètre (✗). Les cases à droite servent à cocher une ligne « revue » sur cet appareil — le détail technique long reste dans le dépôt.",
|
|
56
56
|
statusKeyLine:
|
|
57
57
|
"✓ vert = implémenté dans le produit · ✗ rouge = non livré ou volontairement exclu.",
|
|
58
|
-
lastUpdated: "Dernière mise à jour : 2026-05-
|
|
58
|
+
lastUpdated: "Dernière mise à jour : 2026-05-13 (v1.0.0-beta.22)",
|
|
59
59
|
repositoryDocLabel: "Document dépôt : docs/IMPLEMENTATION_DETAILS.md",
|
|
60
60
|
storyGroupsHeading: "Inventaire des user stories",
|
|
61
61
|
implementationColumnLabel: "Implémentation",
|
|
@@ -92,7 +92,7 @@ const frBundle: ImplementationNotesBundle = {
|
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
implemented: true,
|
|
95
|
-
text: "Pour une livraison donnée (p. ex. 1.0.0-beta.
|
|
95
|
+
text: "Pour une livraison donnée (p. ex. 1.0.0-beta.22), valider le comportement en croisant la section correspondante du **CHANGELOG**, les récits marqués livrés sur cette page et `docs/IMPLEMENTATION_DETAILS.md`, puis cocher au besoin **intégration** / **E2E**.",
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
implemented: true,
|
|
@@ -499,15 +499,17 @@ const frBundle: ImplementationNotesBundle = {
|
|
|
499
499
|
implemented: true,
|
|
500
500
|
integrationChecked: true,
|
|
501
501
|
e2eChecked: false,
|
|
502
|
-
text: "Sur la page Rapports,
|
|
502
|
+
text: "Sur la page Rapports, basculer la grille hebdomadaire par projet entre le registre travail (@) et le registre personnel (!) avec les mêmes filtres de plage et d’étiquettes.",
|
|
503
503
|
detail:
|
|
504
|
-
"`app/reporting/page.tsx` appelle `aggregateProjectTaskMinutesByDay` avec le scope
|
|
504
|
+
"`app/reporting/page.tsx` appelle `aggregateProjectTaskMinutesByDay` avec le scope `work` ou `personal` selon le sélecteur (`lib/reportingAggregate.ts`, `taskMatchesReportingProjectScope`, `taskIsReportingPersonalLedger`). La vue **Travail** (défaut) exclut le registre personnel ; la vue **Personnel** ne l’inclut qu’elles, avec libellés `!` via `formatProjectDisplay`. Une tâche est **personnelle** pour l’agrégat si `personalProject` est vrai **ou** si le **titre** contient un jeton `!` reconnu (repli lorsque le drapeau manque sur d’anciennes données).",
|
|
505
505
|
},
|
|
506
506
|
{
|
|
507
|
-
implemented:
|
|
508
|
-
|
|
507
|
+
implemented: true,
|
|
508
|
+
integrationChecked: true,
|
|
509
|
+
e2eChecked: false,
|
|
510
|
+
text: "Consulter par défaut les grilles par projet en temps productif (@) sans y mélanger le temps des projets personnels (!).",
|
|
509
511
|
detail:
|
|
510
|
-
"
|
|
512
|
+
"Au chargement, le sélecteur est sur **Travail (@)** : même comportement qu’auparavant (scope `work` seul).",
|
|
511
513
|
},
|
|
512
514
|
{
|
|
513
515
|
implemented: true,
|
|
@@ -622,7 +624,7 @@ const enBundle: ImplementationNotesBundle = {
|
|
|
622
624
|
"An exhaustive list of user intentions: each row shows whether the capability is shipped (✓) or missing / out of scope (✗). Checkboxes on the right mark a row as reviewed on this device — long technical detail stays in the repo doc.",
|
|
623
625
|
statusKeyLine:
|
|
624
626
|
"Green ✓ = implemented in the product · Red ✗ = not shipped or intentionally excluded.",
|
|
625
|
-
lastUpdated: "Last updated: 2026-05-
|
|
627
|
+
lastUpdated: "Last updated: 2026-05-13 (v1.0.0-beta.22)",
|
|
626
628
|
repositoryDocLabel: "Repository document: docs/IMPLEMENTATION_DETAILS.md",
|
|
627
629
|
storyGroupsHeading: "User story inventory",
|
|
628
630
|
implementationColumnLabel: "Implementation",
|
|
@@ -659,7 +661,7 @@ const enBundle: ImplementationNotesBundle = {
|
|
|
659
661
|
},
|
|
660
662
|
{
|
|
661
663
|
implemented: true,
|
|
662
|
-
text: "For a given release (e.g. 1.0.0-beta.
|
|
664
|
+
text: "For a given release (e.g. 1.0.0-beta.22), cross-check the matching **CHANGELOG** section, the shipped stories on this page and `docs/IMPLEMENTATION_DETAILS.md`, then tick **Integration** / **E2E** as appropriate.",
|
|
663
665
|
},
|
|
664
666
|
{
|
|
665
667
|
implemented: true,
|
|
@@ -1066,15 +1068,17 @@ const enBundle: ImplementationNotesBundle = {
|
|
|
1066
1068
|
implemented: true,
|
|
1067
1069
|
integrationChecked: true,
|
|
1068
1070
|
e2eChecked: false,
|
|
1069
|
-
text: "On the Reporting page,
|
|
1071
|
+
text: "On the Reporting page, switch the weekly-by-project grid between the work (@) ledger and the personal (!) ledger with the same date-range and tag filters.",
|
|
1070
1072
|
detail:
|
|
1071
|
-
"`app/reporting/page.tsx` calls `aggregateProjectTaskMinutesByDay` with
|
|
1073
|
+
"`app/reporting/page.tsx` calls `aggregateProjectTaskMinutesByDay` with scope `work` or `personal` from the toggle (`lib/reportingAggregate.ts`, `taskMatchesReportingProjectScope`, `taskIsReportingPersonalLedger`). **Work** (default) excludes the personal ledger; **Personal** includes only it, with `!` labels via `formatProjectDisplay`. A task is **personal** for aggregates when `personalProject` is true **or** when the **title** contains a recognized `!` token (fallback when the flag is missing on older rows).",
|
|
1072
1074
|
},
|
|
1073
1075
|
{
|
|
1074
|
-
implemented:
|
|
1075
|
-
|
|
1076
|
+
implemented: true,
|
|
1077
|
+
integrationChecked: true,
|
|
1078
|
+
e2eChecked: false,
|
|
1079
|
+
text: "By default, per-project grids show productive (@) time without mixing in personal (!) minutes.",
|
|
1076
1080
|
detail:
|
|
1077
|
-
"
|
|
1081
|
+
"On first load the toggle is **Work (@)** — same behaviour as before (work scope only).",
|
|
1078
1082
|
},
|
|
1079
1083
|
{
|
|
1080
1084
|
implemented: true,
|
|
@@ -2,9 +2,12 @@ import {
|
|
|
2
2
|
DEFAULT_FALLBACK_TASK_TAG,
|
|
3
3
|
formatProjectDisplay,
|
|
4
4
|
formatTagDisplay,
|
|
5
|
+
formatTagDisplayForTask,
|
|
5
6
|
isFallbackTaskTagKey,
|
|
6
7
|
normalizeProjectKey,
|
|
7
8
|
normalizeTagKey,
|
|
9
|
+
parseProjectScopedTag,
|
|
10
|
+
resolvePersonalProjectForTaskUpdate,
|
|
8
11
|
} from "./taskParsing";
|
|
9
12
|
import type { KronosysUpdatePayload } from "./kronosysApi";
|
|
10
13
|
import { LEGACY_TASK_CYCLES_KEY, LEGACY_TASK_USED_FLAG_KEY } from "./legacyKronoFocusStorageKeys";
|
|
@@ -21,6 +24,8 @@ import { normalizeSessionEndReasonKind } from "./sessionEndReason";
|
|
|
21
24
|
|
|
22
25
|
export type LooseTask = {
|
|
23
26
|
id?: string;
|
|
27
|
+
/** Titre brut ; utilisé pour inférer `!` si `personalProject` est absent ou obsolète. */
|
|
28
|
+
name?: string;
|
|
24
29
|
startTime?: string;
|
|
25
30
|
endTime?: string;
|
|
26
31
|
durationMs?: number;
|
|
@@ -38,11 +43,26 @@ export type LooseTask = {
|
|
|
38
43
|
/** Filtre les tâches pour les agrégats « @ projet » vs « ! projet personnel ». */
|
|
39
44
|
export type ReportingTaskProjectScope = "all" | "work" | "personal";
|
|
40
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Registre personnel pour le reporting : drapeau persisté **ou** jeton `!` déduit du titre
|
|
48
|
+
* (rattrapage si `personalProject` n’a pas été enregistré sur d’anciennes tâches).
|
|
49
|
+
*/
|
|
50
|
+
export function taskIsReportingPersonalLedger(task: LooseTask): boolean {
|
|
51
|
+
if (task.personalProject === true) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
const rawName = typeof task.name === "string" ? task.name.trim() : "";
|
|
55
|
+
if (!rawName) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return resolvePersonalProjectForTaskUpdate(rawName) === true;
|
|
59
|
+
}
|
|
60
|
+
|
|
41
61
|
export function taskMatchesReportingProjectScope(
|
|
42
62
|
task: LooseTask,
|
|
43
63
|
scope: ReportingTaskProjectScope,
|
|
44
64
|
): boolean {
|
|
45
|
-
const isP = task
|
|
65
|
+
const isP = taskIsReportingPersonalLedger(task);
|
|
46
66
|
if (scope === "all") {
|
|
47
67
|
return true;
|
|
48
68
|
}
|
|
@@ -424,6 +444,48 @@ function mergeIntervalsToMinutes(intervals: Array<[number, number]>): number {
|
|
|
424
444
|
return totalMs / 60000;
|
|
425
445
|
}
|
|
426
446
|
|
|
447
|
+
/** Entrée d’étiquette pour agrégats : clé canonique (sépare @ / ! sur `projet#suffixe`) + libellé. */
|
|
448
|
+
function taskTagReportingEntry(
|
|
449
|
+
raw: string,
|
|
450
|
+
task: LooseTask,
|
|
451
|
+
fallbackTagDisplay: string,
|
|
452
|
+
): { key: string; display: string } | null {
|
|
453
|
+
const token = normalizeTagKey(String(raw)).trim();
|
|
454
|
+
if (!token) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
const lower = token.toLowerCase();
|
|
458
|
+
if (lower === DEFAULT_FALLBACK_TASK_TAG) {
|
|
459
|
+
return { key: lower, display: fallbackTagDisplay };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const scoped = parseProjectScopedTag(token);
|
|
463
|
+
const taskPersonal = taskIsReportingPersonalLedger(task);
|
|
464
|
+
const tpRaw = typeof task.project === "string" ? task.project.trim() : "";
|
|
465
|
+
const tp = tpRaw ? normalizeProjectKey(tpRaw) : "";
|
|
466
|
+
|
|
467
|
+
if (!scoped) {
|
|
468
|
+
return {
|
|
469
|
+
key: lower,
|
|
470
|
+
display: formatTagDisplayForTask(token, undefined, {
|
|
471
|
+
personalProject: taskPersonal,
|
|
472
|
+
taskProject: tp || null,
|
|
473
|
+
}),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const pk = normalizeProjectKey(scoped.projectKey);
|
|
478
|
+
const loc = scoped.localTag;
|
|
479
|
+
const sameProject = tp.length > 0 && pk.toLowerCase() === tp.toLowerCase();
|
|
480
|
+
const sigil: "@" | "!" = sameProject ? (taskPersonal ? "!" : "@") : "@";
|
|
481
|
+
const key = `${sigil}${pk}#${loc}`.toLowerCase();
|
|
482
|
+
const display = formatTagDisplayForTask(token, undefined, {
|
|
483
|
+
personalProject: taskPersonal,
|
|
484
|
+
taskProject: tp || null,
|
|
485
|
+
});
|
|
486
|
+
return { key, display };
|
|
487
|
+
}
|
|
488
|
+
|
|
427
489
|
function uniqueTaskTagEntries(
|
|
428
490
|
task: LooseTask,
|
|
429
491
|
fallbackTagDisplay: string
|
|
@@ -432,18 +494,15 @@ function uniqueTaskTagEntries(
|
|
|
432
494
|
const seen = new Set<string>();
|
|
433
495
|
const out: Array<{ key: string; display: string }> = [];
|
|
434
496
|
for (const raw of tags) {
|
|
435
|
-
const
|
|
436
|
-
if (!
|
|
497
|
+
const one = taskTagReportingEntry(raw, task, fallbackTagDisplay);
|
|
498
|
+
if (!one) {
|
|
437
499
|
continue;
|
|
438
500
|
}
|
|
439
|
-
|
|
440
|
-
if (seen.has(key)) {
|
|
501
|
+
if (seen.has(one.key)) {
|
|
441
502
|
continue;
|
|
442
503
|
}
|
|
443
|
-
seen.add(key);
|
|
444
|
-
|
|
445
|
-
key === DEFAULT_FALLBACK_TASK_TAG ? fallbackTagDisplay : formatTagDisplay(token);
|
|
446
|
-
out.push({ key, display });
|
|
504
|
+
seen.add(one.key);
|
|
505
|
+
out.push({ key: one.key, display: one.display });
|
|
447
506
|
}
|
|
448
507
|
return out;
|
|
449
508
|
}
|
|
@@ -220,17 +220,17 @@ export const reportingMetricHelpEn: ReportingMetricHelpBlock = {
|
|
|
220
220
|
"Sum of attributed task minutes for that tag and period. Minutes are not split across tags: a multi-tag task contributes its full duration to each tag row.",
|
|
221
221
|
metricHelpProjectTitleAria: "Help: recorded time by project",
|
|
222
222
|
metricHelpProjectTitleBody:
|
|
223
|
-
"Recorded task duration (durationMs) split by each task’s
|
|
223
|
+
"Recorded task duration (durationMs) split by each task’s project field, shown as a weekly calendar: one period line per week, then one table with a row per project and seven day columns plus a week total. Use the Work (@) / Personal (!) control to choose the ledger: Work (default) counts only non-personal tasks; Personal counts tasks marked personal (`personalProject`) and tasks whose title contains a recognized `!` project token (fallback when the flag was not saved), with `!` labels. Same date range, tag filter, archived-session rules, and week navigation as the tag calendar.",
|
|
224
224
|
metricHelpProjectCalendarAria: "Help: project weekly calendar",
|
|
225
225
|
metricHelpProjectCalendarBody:
|
|
226
|
-
"Same layout as the tag calendar: for each week, the date span appears once; each row is one
|
|
226
|
+
"Same layout as the tag calendar: for each week, the date span appears once; each row is one project for the selected ledger (Work or Personal). Column headers show weekday and calendar date; day cells use task day (end or start); hover a header or cell for YYYY-MM-DD.",
|
|
227
227
|
metricHelpProjectColProjAria: "Help: project column",
|
|
228
|
-
metricHelpProjectColProjBody: "Project name from the task, or “no project” when none is set.",
|
|
228
|
+
metricHelpProjectColProjBody: "Project name from the task (with @ or ! depending on the ledger), or “no project” when none is set.",
|
|
229
229
|
metricHelpProjectColTasksAria: "Help: tasks column",
|
|
230
230
|
metricHelpProjectColTasksBody: "Number of task rows summed into that project row.",
|
|
231
231
|
metricHelpProjectColTimeAria: "Help: recorded time column",
|
|
232
232
|
metricHelpProjectColTimeBody:
|
|
233
|
-
"Total recorded task minutes for that
|
|
233
|
+
"Total recorded task minutes for that project over the seven days of the row (from durationMs), for the selected Work or Personal ledger.",
|
|
234
234
|
metricHelpWorkspaceTitleAria: "Help: open folder snapshot",
|
|
235
235
|
metricHelpWorkspaceTitleBody:
|
|
236
236
|
"Snapshot of the first workspace folder: line counts by language from Git-tracked files or a folder walk. Not tied to Kronosys sessions; cached ~5 minutes. See the paragraph below for details.",
|
|
@@ -378,17 +378,17 @@ export const reportingMetricHelpFr: ReportingMetricHelpBlock = {
|
|
|
378
378
|
"Somme des minutes de tâche attribuées à cette étiquette sur la période de la ligne. Les minutes ne sont pas réparties entre les étiquettes : une tâche multi-étiquettes compte sa durée entière sur chaque ligne d’étiquette.",
|
|
379
379
|
metricHelpProjectTitleAria: "Aide : temps enregistré par projet",
|
|
380
380
|
metricHelpProjectTitleBody:
|
|
381
|
-
"Durée enregistrée sur les tâches (durationMs) ventilée par
|
|
381
|
+
"Durée enregistrée sur les tâches (durationMs) ventilée par le champ projet de chaque tâche, sous forme de calendrier hebdomadaire : une ligne de période par semaine, puis un tableau avec une ligne par projet et sept colonnes jour plus le total de la semaine. Utilisez le sélecteur Travail (@) / Personnel (!) pour choisir le registre : Travail (défaut) ne compte que les tâches non personnelles ; Personnel compte les tâches marquées personnelles (`personalProject`) et celles dont le titre contient un jeton `!` reconnu (repli si le drapeau n’a pas été enregistré), avec libellés « ! ». Mêmes plage de dates, filtre d’étiquettes, règles d’archivage et navigation par semaine que le calendrier par étiquette.",
|
|
382
382
|
metricHelpProjectCalendarAria: "Aide : calendrier hebdomadaire par projet",
|
|
383
383
|
metricHelpProjectCalendarBody:
|
|
384
|
-
"Même présentation que le calendrier par étiquette : pour chaque semaine, la plage de dates s’affiche une fois ; chaque ligne correspond à un
|
|
384
|
+
"Même présentation que le calendrier par étiquette : pour chaque semaine, la plage de dates s’affiche une fois ; chaque ligne correspond à un projet pour le registre choisi (Travail ou Personnel). Les en-têtes de colonnes indiquent le jour de la semaine et la date calendaire ; les cases jour utilisent le jour de la tâche (fin ou début) ; survol d’un en-tête ou d’une case pour AAAA-MM-JJ.",
|
|
385
385
|
metricHelpProjectColProjAria: "Aide : colonne projet",
|
|
386
|
-
metricHelpProjectColProjBody: "Nom du projet sur la tâche, ou « sans projet » si absent.",
|
|
386
|
+
metricHelpProjectColProjBody: "Nom du projet sur la tâche (avec @ ou ! selon le registre), ou « sans projet » si absent.",
|
|
387
387
|
metricHelpProjectColTasksAria: "Aide : colonne tâches",
|
|
388
388
|
metricHelpProjectColTasksBody: "Nombre de lignes de tâches agrégées pour ce projet.",
|
|
389
389
|
metricHelpProjectColTimeAria: "Aide : colonne temps enregistré",
|
|
390
390
|
metricHelpProjectColTimeBody:
|
|
391
|
-
"Total des minutes enregistrées sur les tâches pour ce
|
|
391
|
+
"Total des minutes enregistrées sur les tâches pour ce projet sur les sept jours de la ligne (durationMs), pour le registre Travail ou Personnel sélectionné.",
|
|
392
392
|
metricHelpWorkspaceTitleAria: "Aide : instantané du dossier ouvert",
|
|
393
393
|
metricHelpWorkspaceTitleBody:
|
|
394
394
|
"Instantané du premier dossier du workspace : lignes par langage à partir des fichiers suivis par Git ou d’un parcours du dossier. Indépendant des sessions Kronosys ; mis en cache ~5 minutes. Voir le paragraphe descriptif sous le titre.",
|