@nightkatana/kronosys-app 1.0.0-beta.12 → 1.0.0-beta.13
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.
|
@@ -8,12 +8,17 @@ import type { KronosysUpdatePayload } from "@/lib/kronosysApi";
|
|
|
8
8
|
import type { DashboardStrings, Lang } from "@/lib/dashboardCopy";
|
|
9
9
|
import {
|
|
10
10
|
buildStartTaskFromDraft,
|
|
11
|
+
formatDuration,
|
|
11
12
|
formatProjectDisplay,
|
|
12
13
|
mergeTagsForDisplay,
|
|
14
|
+
normalizeProjectKey,
|
|
15
|
+
normalizeTagKey,
|
|
13
16
|
parseTaskWithAutoTags,
|
|
14
17
|
removeProjectFromDraft,
|
|
15
18
|
removeSavedTagFromDraft,
|
|
19
|
+
taskTitleForDisplay,
|
|
16
20
|
} from "@/lib/taskParsing";
|
|
21
|
+
import { formatIsoInstantShort } from "@/lib/formatIsoShort";
|
|
17
22
|
import { TagPills } from "./TagPills";
|
|
18
23
|
import {
|
|
19
24
|
PROJECT_CHIP_APPLIED_CLASS,
|
|
@@ -88,8 +93,8 @@ function runningTasksFromSession(
|
|
|
88
93
|
Array.isArray(session.activeTasks) && session.activeTasks.length > 0
|
|
89
94
|
? session.activeTasks
|
|
90
95
|
: session.activeTask
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
? [session.activeTask]
|
|
97
|
+
: [];
|
|
93
98
|
return raw.filter((t) => t && !t.isDone && !t.manualTaskTimerPaused);
|
|
94
99
|
}
|
|
95
100
|
|
|
@@ -156,9 +161,9 @@ export function TaskFocusPanel({
|
|
|
156
161
|
const knownTags = (payload.knownTags || []) as string[];
|
|
157
162
|
const knownProjects = (payload.knownProjects || []) as string[];
|
|
158
163
|
const viewingSession = archiveColumnId
|
|
159
|
-
?
|
|
164
|
+
? history.find((s) => s.sessionId === archiveColumnId) ??
|
|
160
165
|
historyArchivedList.find((s) => s.sessionId === archiveColumnId) ??
|
|
161
|
-
null
|
|
166
|
+
null
|
|
162
167
|
: null;
|
|
163
168
|
const sessionCurrent = viewingSession ?? live;
|
|
164
169
|
const isInspecting = !!viewingSession;
|
|
@@ -217,8 +222,8 @@ export function TaskFocusPanel({
|
|
|
217
222
|
Array.isArray(sess.activeTasks) && sess.activeTasks.length > 0
|
|
218
223
|
? [...(sess.activeTasks as TaskRow[])]
|
|
219
224
|
: sess.activeTask
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
? [sess.activeTask as TaskRow]
|
|
226
|
+
: [];
|
|
222
227
|
for (const t of stack) {
|
|
223
228
|
if (t?.id) {
|
|
224
229
|
map.set(String(t.id), t);
|
|
@@ -227,6 +232,78 @@ export function TaskFocusPanel({
|
|
|
227
232
|
return [...map.values()];
|
|
228
233
|
}, [taskSessionForBuckets]);
|
|
229
234
|
|
|
235
|
+
const taskTimelineEntries = useMemo(() => {
|
|
236
|
+
const rows = mergedTasksForBuckets
|
|
237
|
+
.map((task) => {
|
|
238
|
+
const startMs =
|
|
239
|
+
typeof task.startTime === "string"
|
|
240
|
+
? Date.parse(task.startTime)
|
|
241
|
+
: Number.NaN;
|
|
242
|
+
const endMs =
|
|
243
|
+
typeof task.endTime === "string"
|
|
244
|
+
? Date.parse(task.endTime)
|
|
245
|
+
: Number.NaN;
|
|
246
|
+
if (!Number.isFinite(startMs) && !Number.isFinite(endMs)) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const startLabel = Number.isFinite(startMs)
|
|
250
|
+
? formatIsoInstantShort(
|
|
251
|
+
new Date(startMs).toISOString(),
|
|
252
|
+
lang,
|
|
253
|
+
displayTimeZone,
|
|
254
|
+
use24HourClock,
|
|
255
|
+
)
|
|
256
|
+
: "—";
|
|
257
|
+
const endLabel = Number.isFinite(endMs)
|
|
258
|
+
? formatIsoInstantShort(
|
|
259
|
+
new Date(endMs).toISOString(),
|
|
260
|
+
lang,
|
|
261
|
+
displayTimeZone,
|
|
262
|
+
use24HourClock,
|
|
263
|
+
)
|
|
264
|
+
: isInspecting
|
|
265
|
+
? "—"
|
|
266
|
+
: lang === "fr"
|
|
267
|
+
? "en cours"
|
|
268
|
+
: "running";
|
|
269
|
+
const durationMin =
|
|
270
|
+
typeof task.durationMs === "number" &&
|
|
271
|
+
Number.isFinite(task.durationMs)
|
|
272
|
+
? task.durationMs / 60000
|
|
273
|
+
: Number.NaN;
|
|
274
|
+
return {
|
|
275
|
+
id: String(task.id),
|
|
276
|
+
name: task.name,
|
|
277
|
+
project: task.project ?? null,
|
|
278
|
+
startSort: Number.isFinite(startMs)
|
|
279
|
+
? startMs
|
|
280
|
+
: Number.isFinite(endMs)
|
|
281
|
+
? endMs
|
|
282
|
+
: Number.MAX_SAFE_INTEGER,
|
|
283
|
+
endSort: Number.isFinite(endMs)
|
|
284
|
+
? endMs
|
|
285
|
+
: Number.isFinite(startMs)
|
|
286
|
+
? startMs
|
|
287
|
+
: Number.MAX_SAFE_INTEGER,
|
|
288
|
+
startLabel,
|
|
289
|
+
endLabel,
|
|
290
|
+
durationLabel:
|
|
291
|
+
Number.isFinite(durationMin) && durationMin > 0
|
|
292
|
+
? formatDuration(durationMin)
|
|
293
|
+
: null,
|
|
294
|
+
};
|
|
295
|
+
})
|
|
296
|
+
.filter((entry): entry is NonNullable<typeof entry> => entry !== null);
|
|
297
|
+
rows.sort((a, b) => a.startSort - b.startSort || a.endSort - b.endSort);
|
|
298
|
+
return rows;
|
|
299
|
+
}, [
|
|
300
|
+
mergedTasksForBuckets,
|
|
301
|
+
lang,
|
|
302
|
+
displayTimeZone,
|
|
303
|
+
use24HourClock,
|
|
304
|
+
isInspecting,
|
|
305
|
+
]);
|
|
306
|
+
|
|
230
307
|
/**
|
|
231
308
|
* Bandeau TRACKING : uniquement si la collecte n’est pas en pause session
|
|
232
309
|
* (sinon le minuteur de suivi n’est pas actif, mais l’interpolation locale faisait défiler l’horloge).
|
|
@@ -356,6 +433,39 @@ export function TaskFocusPanel({
|
|
|
356
433
|
setStartKronoFocusWithTask(false);
|
|
357
434
|
}, []);
|
|
358
435
|
|
|
436
|
+
const duplicateTaskToDraft = useCallback(
|
|
437
|
+
(task: TaskRow) => {
|
|
438
|
+
const targetSessionId = inspectingId ?? live?.sessionId;
|
|
439
|
+
const title = taskTitleForDisplay(task.name).trim();
|
|
440
|
+
const tags = (task.tags ?? []).map((tag) => `#${normalizeTagKey(tag)}`);
|
|
441
|
+
const project =
|
|
442
|
+
typeof task.project === "string" && task.project.trim()
|
|
443
|
+
? `@${normalizeProjectKey(task.project)}`
|
|
444
|
+
: "";
|
|
445
|
+
const nextInput = [title, ...tags, project]
|
|
446
|
+
.filter(Boolean)
|
|
447
|
+
.join(" ")
|
|
448
|
+
.trim();
|
|
449
|
+
setTaskInput(nextInput);
|
|
450
|
+
if (
|
|
451
|
+
typeof task.startTime === "string" &&
|
|
452
|
+
task.startTime.trim() &&
|
|
453
|
+
typeof task.endTime === "string" &&
|
|
454
|
+
task.endTime.trim() &&
|
|
455
|
+
targetSessionId
|
|
456
|
+
) {
|
|
457
|
+
const start = new Date(task.startTime);
|
|
458
|
+
const end = new Date(task.endTime);
|
|
459
|
+
if (!Number.isNaN(start.getTime()) && !Number.isNaN(end.getTime())) {
|
|
460
|
+
setTaskEntryMode("past");
|
|
461
|
+
setPastStartLocal(formatDatetimeLocalValue(start));
|
|
462
|
+
setPastEndLocal(formatDatetimeLocalValue(end));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
[inspectingId, live?.sessionId],
|
|
467
|
+
);
|
|
468
|
+
|
|
359
469
|
const resolveConcurrentAndStartLive = useCallback(
|
|
360
470
|
async (mode: ConcurrentTaskStartPreference, persistPreference: boolean) => {
|
|
361
471
|
const draft = pendingLiveStartRef.current;
|
|
@@ -397,8 +507,8 @@ export function TaskFocusPanel({
|
|
|
397
507
|
typeof error === "string"
|
|
398
508
|
? error
|
|
399
509
|
: error instanceof Error
|
|
400
|
-
|
|
401
|
-
|
|
510
|
+
? error.message
|
|
511
|
+
: "Failed to resolve concurrent tasks",
|
|
402
512
|
);
|
|
403
513
|
pendingLiveStartRef.current = null;
|
|
404
514
|
setConcurrentStartModalOpen(false);
|
|
@@ -1061,6 +1171,7 @@ export function TaskFocusPanel({
|
|
|
1061
1171
|
startKronoFocusFromTask={() =>
|
|
1062
1172
|
void startKronoFocusFromTask()
|
|
1063
1173
|
}
|
|
1174
|
+
onDuplicateTask={duplicateTaskToDraft}
|
|
1064
1175
|
pausePlayMode="pause"
|
|
1065
1176
|
onPausePlay={() =>
|
|
1066
1177
|
void post({
|
|
@@ -1120,6 +1231,7 @@ export function TaskFocusPanel({
|
|
|
1120
1231
|
startKronoFocusFromTask={() =>
|
|
1121
1232
|
void startKronoFocusFromTask()
|
|
1122
1233
|
}
|
|
1234
|
+
onDuplicateTask={duplicateTaskToDraft}
|
|
1123
1235
|
pausePlayMode="resume"
|
|
1124
1236
|
onPausePlay={() =>
|
|
1125
1237
|
void post({ type: "resumePausedTask", taskId: task.id })
|
|
@@ -1163,6 +1275,7 @@ export function TaskFocusPanel({
|
|
|
1163
1275
|
startKronoFocusFromTask={() =>
|
|
1164
1276
|
void startKronoFocusFromTask()
|
|
1165
1277
|
}
|
|
1278
|
+
onDuplicateTask={duplicateTaskToDraft}
|
|
1166
1279
|
pausePlayMode={null}
|
|
1167
1280
|
onPausePlay={() => {}}
|
|
1168
1281
|
anchorId={`kronosys-task-${task.id}`}
|
|
@@ -1176,6 +1289,56 @@ export function TaskFocusPanel({
|
|
|
1176
1289
|
</div>
|
|
1177
1290
|
) : null}
|
|
1178
1291
|
|
|
1292
|
+
{isInspecting || hasLiveSession ? (
|
|
1293
|
+
<section className="mt-10 space-y-2">
|
|
1294
|
+
<div className="flex items-center justify-between gap-2">
|
|
1295
|
+
<h4 className="text-xs font-semibold uppercase tracking-wide text-zinc-500">
|
|
1296
|
+
{t.tasksTimelineHeading}
|
|
1297
|
+
</h4>
|
|
1298
|
+
{taskTimelineEntries.length > 0 ? (
|
|
1299
|
+
<span className="rounded-full border border-zinc-600/80 px-2 py-0.5 text-[0.65rem] text-zinc-500">
|
|
1300
|
+
{taskTimelineEntries.length}
|
|
1301
|
+
</span>
|
|
1302
|
+
) : null}
|
|
1303
|
+
</div>
|
|
1304
|
+
{taskTimelineEntries.length === 0 ? (
|
|
1305
|
+
<p className="text-sm text-zinc-500">{t.tasksTimelineEmpty}</p>
|
|
1306
|
+
) : (
|
|
1307
|
+
<div className="space-y-2">
|
|
1308
|
+
{taskTimelineEntries.map((entry) => (
|
|
1309
|
+
<article
|
|
1310
|
+
key={`timeline-${entry.id}`}
|
|
1311
|
+
className="rounded-lg border border-zinc-200/80 bg-zinc-100/70 px-3 py-2 dark:border-zinc-700/80 dark:bg-zinc-900/40"
|
|
1312
|
+
>
|
|
1313
|
+
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-zinc-500 dark:text-zinc-400">
|
|
1314
|
+
<span className="font-mono tabular-nums">
|
|
1315
|
+
{entry.startLabel}
|
|
1316
|
+
</span>
|
|
1317
|
+
<span>→</span>
|
|
1318
|
+
<span className="font-mono tabular-nums">
|
|
1319
|
+
{entry.endLabel}
|
|
1320
|
+
</span>
|
|
1321
|
+
{entry.durationLabel ? (
|
|
1322
|
+
<span className="rounded-full border border-zinc-500/40 px-1.5 py-0.5 text-[0.65rem]">
|
|
1323
|
+
{entry.durationLabel}
|
|
1324
|
+
</span>
|
|
1325
|
+
) : null}
|
|
1326
|
+
</div>
|
|
1327
|
+
<p className="mt-1 text-sm text-zinc-900 dark:text-zinc-100">
|
|
1328
|
+
{taskTitleForDisplay(entry.name)}
|
|
1329
|
+
</p>
|
|
1330
|
+
{typeof entry.project === "string" && entry.project.trim() ? (
|
|
1331
|
+
<p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400">
|
|
1332
|
+
{formatProjectDisplay(entry.project)}
|
|
1333
|
+
</p>
|
|
1334
|
+
) : null}
|
|
1335
|
+
</article>
|
|
1336
|
+
))}
|
|
1337
|
+
</div>
|
|
1338
|
+
)}
|
|
1339
|
+
</section>
|
|
1340
|
+
) : null}
|
|
1341
|
+
|
|
1179
1342
|
{issuePickerOpen ? (
|
|
1180
1343
|
<IssuePickerModal
|
|
1181
1344
|
t={t}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
CheckCircle2,
|
|
9
9
|
Trash2,
|
|
10
10
|
GitCommit,
|
|
11
|
+
Copy,
|
|
11
12
|
} from "lucide-react";
|
|
12
13
|
import type { DashboardStrings, Lang } from "@/lib/dashboardCopy";
|
|
13
14
|
import { formatIsoInstantShort } from "@/lib/formatIsoShort";
|
|
@@ -181,6 +182,7 @@ export function TaskSessionLiveCard({
|
|
|
181
182
|
kronoFocusIsRunningOrPaused,
|
|
182
183
|
showKronoFocusTaskActions = true,
|
|
183
184
|
startKronoFocusFromTask,
|
|
185
|
+
onDuplicateTask,
|
|
184
186
|
pausePlayMode,
|
|
185
187
|
onPausePlay,
|
|
186
188
|
anchorId,
|
|
@@ -203,6 +205,7 @@ export function TaskSessionLiveCard({
|
|
|
203
205
|
/** Bouton « démarrer le KronoFocus » sur les tâches non terminées (paramètre tableau de bord). */
|
|
204
206
|
showKronoFocusTaskActions?: boolean;
|
|
205
207
|
startKronoFocusFromTask: () => void;
|
|
208
|
+
onDuplicateTask: (task: TaskSessionLiveCardTask) => void;
|
|
206
209
|
/** `null` : tâche terminée (pas de pause / reprise). */
|
|
207
210
|
pausePlayMode: "pause" | "resume" | null;
|
|
208
211
|
onPausePlay: () => void;
|
|
@@ -283,12 +286,12 @@ export function TaskSessionLiveCard({
|
|
|
283
286
|
const showDoneTimingRow =
|
|
284
287
|
isDone && (startFmt !== null || endFmt !== null || durationLabel !== null);
|
|
285
288
|
const canEditTaskStartTime =
|
|
286
|
-
allowTaskStartTimeEdit &&
|
|
289
|
+
(isDone ? true : allowTaskStartTimeEdit) &&
|
|
287
290
|
typeof task.startTime === "string" &&
|
|
288
291
|
task.startTime.trim() !== "";
|
|
289
292
|
const canEditTaskEndTime =
|
|
290
|
-
allowTaskEndTimeEdit &&
|
|
291
293
|
isDone &&
|
|
294
|
+
(isDone ? true : allowTaskEndTimeEdit) &&
|
|
292
295
|
typeof task.startTime === "string" &&
|
|
293
296
|
task.startTime.trim() !== "" &&
|
|
294
297
|
typeof task.endTime === "string" &&
|
|
@@ -621,15 +624,26 @@ export function TaskSessionLiveCard({
|
|
|
621
624
|
</span>
|
|
622
625
|
) : null}
|
|
623
626
|
{isInspecting ? (
|
|
624
|
-
<
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
627
|
+
<div className="flex h-10 shrink-0 items-center gap-1">
|
|
628
|
+
<button
|
|
629
|
+
type="button"
|
|
630
|
+
className="inline-flex h-10 w-10 items-center justify-center rounded-lg border border-zinc-300 bg-white text-zinc-700 hover:bg-zinc-100 dark:border-zinc-600 dark:bg-transparent dark:text-zinc-300 dark:hover:bg-zinc-800"
|
|
631
|
+
title={t.taskDuplicateBtn}
|
|
632
|
+
aria-label={t.taskDuplicateBtn}
|
|
633
|
+
onClick={() => onDuplicateTask(task)}
|
|
634
|
+
>
|
|
635
|
+
<Copy size={18} />
|
|
636
|
+
</button>
|
|
637
|
+
<button
|
|
638
|
+
type="button"
|
|
639
|
+
className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg border border-zinc-300 text-zinc-600 hover:border-red-600/60 hover:bg-red-50 hover:text-red-800 dark:border-zinc-600 dark:text-zinc-400 dark:hover:border-red-800/70 dark:hover:bg-red-950/45 dark:hover:text-red-300"
|
|
640
|
+
title={t.taskDeleteBtn}
|
|
641
|
+
aria-label={t.taskDeleteBtn}
|
|
642
|
+
onClick={() => confirmDeleteTask(task.id)}
|
|
643
|
+
>
|
|
644
|
+
<Trash2 size={20} />
|
|
645
|
+
</button>
|
|
646
|
+
</div>
|
|
633
647
|
) : (
|
|
634
648
|
<div className="flex h-10 shrink-0 items-center gap-1">
|
|
635
649
|
{!isDone && showKronoFocusTaskActions ? (
|
|
@@ -706,6 +720,15 @@ export function TaskSessionLiveCard({
|
|
|
706
720
|
>
|
|
707
721
|
<CheckCircle2 size={20} />
|
|
708
722
|
</button>
|
|
723
|
+
<button
|
|
724
|
+
type="button"
|
|
725
|
+
className="inline-flex h-10 w-10 items-center justify-center rounded-lg border border-zinc-300 bg-white text-zinc-700 hover:bg-zinc-100 dark:border-zinc-600 dark:bg-transparent dark:text-zinc-300 dark:hover:bg-zinc-800"
|
|
726
|
+
title={t.taskDuplicateBtn}
|
|
727
|
+
aria-label={t.taskDuplicateBtn}
|
|
728
|
+
onClick={() => onDuplicateTask(task)}
|
|
729
|
+
>
|
|
730
|
+
<Copy size={18} />
|
|
731
|
+
</button>
|
|
709
732
|
<button
|
|
710
733
|
type="button"
|
|
711
734
|
className="inline-flex h-10 w-10 items-center justify-center rounded-lg border border-zinc-300 text-zinc-600 hover:border-red-600/60 hover:bg-red-50 hover:text-red-800 dark:border-zinc-600 dark:text-zinc-400 dark:hover:border-red-800/70 dark:hover:bg-red-950/45 dark:hover:text-red-300"
|
package/lib/dashboardCopy.ts
CHANGED
|
@@ -115,6 +115,8 @@ export type DashboardStrings = {
|
|
|
115
115
|
finishTaskOpenSubtasksWarnTitle: string;
|
|
116
116
|
/** Corps du modal : les sous-tâches ouvertes seront toutes marquées terminées. */
|
|
117
117
|
finishTaskOpenSubtasksWarnBody: string;
|
|
118
|
+
/** Dupliquer une tâche vers le formulaire pour retouche avant enregistrement. */
|
|
119
|
+
taskDuplicateBtn: string;
|
|
118
120
|
taskDeleteBtn: string;
|
|
119
121
|
/** Texte complet du modal avant suppression d’une tâche. */
|
|
120
122
|
taskDeleteConfirm: string;
|
|
@@ -141,6 +143,10 @@ export type DashboardStrings = {
|
|
|
141
143
|
tasksCompletedHeading: string;
|
|
142
144
|
/** Sous-titre : tâches au minuteur (sous le formulaire d’ajout). */
|
|
143
145
|
tasksRunningHeading: string;
|
|
146
|
+
/** Titre de la chronologie des tâches de la session affichée. */
|
|
147
|
+
tasksTimelineHeading: string;
|
|
148
|
+
/** Message quand aucune tâche de session n'a d'horodatage exploitable. */
|
|
149
|
+
tasksTimelineEmpty: string;
|
|
144
150
|
hideTaskList: string;
|
|
145
151
|
showAllTasks: string;
|
|
146
152
|
importGitIssue: string;
|
|
@@ -766,6 +772,7 @@ const en: DashboardStrings = {
|
|
|
766
772
|
finishTaskOpenSubtasksWarnTitle: "Finish task?",
|
|
767
773
|
finishTaskOpenSubtasksWarnBody:
|
|
768
774
|
"This task still has open detail items. They will all be marked done when you finish the task.",
|
|
775
|
+
taskDuplicateBtn: "Duplicate to draft",
|
|
769
776
|
taskDeleteBtn: "Delete",
|
|
770
777
|
taskDeleteConfirm:
|
|
771
778
|
"Delete this entry? Recorded time will be lost. This cannot be undone.",
|
|
@@ -786,6 +793,8 @@ const en: DashboardStrings = {
|
|
|
786
793
|
tasksPausedHeading: "Paused",
|
|
787
794
|
tasksCompletedHeading: "Completed",
|
|
788
795
|
tasksRunningHeading: "Running now",
|
|
796
|
+
tasksTimelineHeading: "Task timeline",
|
|
797
|
+
tasksTimelineEmpty: "No dated task entries are available for this session yet.",
|
|
789
798
|
hideTaskList: "Hide list",
|
|
790
799
|
showAllTasks: "Show full list",
|
|
791
800
|
importGitIssue: "Import issue from Git",
|
|
@@ -1328,6 +1337,7 @@ const fr: DashboardStrings = {
|
|
|
1328
1337
|
finishTaskOpenSubtasksWarnTitle: "Terminer la tâche ?",
|
|
1329
1338
|
finishTaskOpenSubtasksWarnBody:
|
|
1330
1339
|
"Il reste des points de détail non cochés. Ils seront tous marqués comme terminés lorsque vous terminez la tâche.",
|
|
1340
|
+
taskDuplicateBtn: "Dupliquer vers le brouillon",
|
|
1331
1341
|
taskDeleteBtn: "Supprimer",
|
|
1332
1342
|
taskDeleteConfirm:
|
|
1333
1343
|
"Supprimer cette entrée ? Le temps enregistré sera perdu. Cette action est irréversible.",
|
|
@@ -1348,6 +1358,9 @@ const fr: DashboardStrings = {
|
|
|
1348
1358
|
tasksPausedHeading: "En pause",
|
|
1349
1359
|
tasksCompletedHeading: "Terminées",
|
|
1350
1360
|
tasksRunningHeading: "En cours (minuteur)",
|
|
1361
|
+
tasksTimelineHeading: "Timeline des tâches",
|
|
1362
|
+
tasksTimelineEmpty:
|
|
1363
|
+
"Aucune entrée datée de tâche n’est encore disponible pour cette session.",
|
|
1351
1364
|
hideTaskList: "Masquer la liste",
|
|
1352
1365
|
showAllTasks: "Afficher toute la liste",
|
|
1353
1366
|
importGitIssue: "Importer une issue Git",
|
|
@@ -6,21 +6,76 @@ export type UserChangelogEntry = {
|
|
|
6
6
|
|
|
7
7
|
export const USER_CHANGELOG_ENTRIES: UserChangelogEntry[] = [
|
|
8
8
|
{
|
|
9
|
-
"version": "
|
|
9
|
+
"version": "1.0.0-beta.12",
|
|
10
10
|
"items": [
|
|
11
|
-
"
|
|
11
|
+
"Correctif du runtime CLI: installation des dépendances avec `--ignore-scripts` pour éviter les erreurs Husky hors dépôt."
|
|
12
12
|
]
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
"version": "
|
|
15
|
+
"version": "1.0.0-beta.11",
|
|
16
16
|
"items": [
|
|
17
|
-
"
|
|
17
|
+
"Correctif Windows du lanceur CLI (PowerShell + installation runtime locale).",
|
|
18
|
+
"Rebuild explicite de `better-sqlite3` dans le runtime local."
|
|
18
19
|
]
|
|
19
20
|
},
|
|
20
21
|
{
|
|
21
|
-
"version": "1.0.0",
|
|
22
|
+
"version": "1.0.0-beta.10",
|
|
22
23
|
"items": [
|
|
23
|
-
"
|
|
24
|
+
"Correctif de dispatch des commandes Windows pour le runtime CLI."
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"version": "1.0.0-beta.9",
|
|
29
|
+
"items": [
|
|
30
|
+
"Correctif de quoting des commandes Windows dans le lanceur CLI."
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"version": "1.0.0-beta.8",
|
|
35
|
+
"items": [
|
|
36
|
+
"Correctif initial Windows du launcher (`npm.cmd`/`npx.cmd`)."
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"version": "1.0.0-beta.7",
|
|
41
|
+
"items": [
|
|
42
|
+
"Stabilisation du packaging bêta et génération des archives de distribution."
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"version": "1.0.0-beta.6",
|
|
47
|
+
"items": [
|
|
48
|
+
"Correctif de résolution d’alias d’import (`@/...`) pour le build de production."
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"version": "1.0.0-beta.5",
|
|
53
|
+
"items": [
|
|
54
|
+
"Retour temporaire au mode build à la première exécution pour améliorer la compatibilité multiplateforme."
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"version": "1.0.0-beta.4",
|
|
59
|
+
"items": [
|
|
60
|
+
"Ajustements de packaging et de lancement CLI pour la série bêta."
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"version": "1.0.0-beta.2",
|
|
65
|
+
"items": [
|
|
66
|
+
"Correctif des dépendances de build nécessaires au runtime CLI."
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"version": "1.0.0-beta.1",
|
|
71
|
+
"items": [
|
|
72
|
+
"`kronosys` lance maintenant le build de production automatiquement si `.next` est absent."
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"version": "1.0.0-beta.0",
|
|
77
|
+
"items": [
|
|
78
|
+
"Première version bêta publiée."
|
|
24
79
|
]
|
|
25
80
|
},
|
|
26
81
|
{
|
package/lib/userGuideCopy.ts
CHANGED
|
@@ -134,6 +134,9 @@ function frBundle(): UserGuideBundle {
|
|
|
134
134
|
"**Terminer la session** quand le bloc a une fin (bouton d’en-tête ou bannière) ; une **raison** de clôture **facultative** (dans les temps, en avance, dépassement, autre + note) : pour poser mots sur la **fin** du cycle — d’**abord** pour vous, pas pour un **audit** froid."
|
|
135
135
|
],
|
|
136
136
|
bullets: [
|
|
137
|
+
"Les tâches **terminées** restent **éditables** (titre, #étiquettes, @projet, heures de début/fin) pour corriger une entrée après coup sans bricolage.",
|
|
138
|
+
"Le bouton **Dupliquer vers le brouillon** copie une tâche dans le formulaire, vous laissez retoucher puis enregistrer la nouvelle entrée quand vous êtes prêt.",
|
|
139
|
+
"Quand vous consultez une session (active ou terminée), une **timeline des tâches** affiche l’enchaînement des entrées datées (début → fin + durée).",
|
|
137
140
|
"L’**option** **Commit** à la fin d’une tâche, quand elle apparaît, concerne **Git** (voir [Synchronisation Git](/settings#settings-git) côté paramètres) : elle n’est **là** que **si** l’intégration est en place — les **équipiers** **non** devs peuvent l’**ignorer** sereinement."
|
|
138
141
|
],
|
|
139
142
|
},
|
|
@@ -332,6 +335,9 @@ function enBundle(): UserGuideBundle {
|
|
|
332
335
|
"**End session** from the header or banner: optional end reason (on time, early, overrun, other + note) — a word for **how** the block **ended**, not a cold verdict.",
|
|
333
336
|
],
|
|
334
337
|
bullets: [
|
|
338
|
+
"**Completed** tasks stay **editable** (title, #tags, @project, start/end times) so you can correct entries after the fact without hacks.",
|
|
339
|
+
"Use **Duplicate to draft** to copy a task into the form, tweak it, then save the new entry only when it looks right.",
|
|
340
|
+
"When viewing a session (live or finished), a **task timeline** shows dated entries in order (start → end + duration).",
|
|
335
341
|
"When **Commit** on **finish** appears, it is about **Git** (see [Git sync](/settings#settings-git) in **Settings**) — you can **ignore** it with a clear head if you are not in that workflow.",
|
|
336
342
|
],
|
|
337
343
|
},
|