@nightkatana/kronosys-app 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/README.md +81 -0
  2. package/app/api/action/route.ts +16 -0
  3. package/app/api/backup/route.ts +84 -0
  4. package/app/api/health/route.ts +22 -0
  5. package/app/api/state/route.ts +27 -0
  6. package/app/apple-icon.png +0 -0
  7. package/app/changelog/page.tsx +122 -0
  8. package/app/globals.css +210 -0
  9. package/app/guide/layout.tsx +11 -0
  10. package/app/guide/page.tsx +278 -0
  11. package/app/icon.png +0 -0
  12. package/app/layout.tsx +77 -0
  13. package/app/licenses/layout.tsx +11 -0
  14. package/app/licenses/page.tsx +246 -0
  15. package/app/manifest.ts +32 -0
  16. package/app/page.tsx +1610 -0
  17. package/app/reporting/page.tsx +2943 -0
  18. package/app/settings/layout.tsx +10 -0
  19. package/app/settings/page.tsx +3518 -0
  20. package/bin/kronosys.mjs +46 -0
  21. package/components/KronosysPackageVersionProvider.tsx +19 -0
  22. package/components/KronosysPayloadProvider.tsx +109 -0
  23. package/components/PwaRegister.tsx +25 -0
  24. package/components/SiteLegalFooter.tsx +21 -0
  25. package/components/ThemeProvider.tsx +78 -0
  26. package/components/dashboard/AppShellLiveSessionDrawer.tsx +394 -0
  27. package/components/dashboard/AppShellRouteNav.tsx +131 -0
  28. package/components/dashboard/AppVersionStamp.tsx +16 -0
  29. package/components/dashboard/DashboardCollapsibleSection.tsx +57 -0
  30. package/components/dashboard/DashboardColumnHintsBanner.tsx +159 -0
  31. package/components/dashboard/DashboardCommandCenter.tsx +470 -0
  32. package/components/dashboard/DashboardLangGateModal.tsx +118 -0
  33. package/components/dashboard/DashboardLoadingOverlay.tsx +42 -0
  34. package/components/dashboard/DashboardSimpleModal.tsx +337 -0
  35. package/components/dashboard/DashboardSuspenseFallback.tsx +52 -0
  36. package/components/dashboard/DashboardToastProvider.tsx +64 -0
  37. package/components/dashboard/DashboardTour.tsx +435 -0
  38. package/components/dashboard/DeferredDescriptionPopoverWrap.tsx +39 -0
  39. package/components/dashboard/DeleteSessionModal.tsx +130 -0
  40. package/components/dashboard/DescriptionTooltipPortaled.tsx +31 -0
  41. package/components/dashboard/GitIdentityQuickSetupModal.tsx +211 -0
  42. package/components/dashboard/HeaderIntegrationBadges.tsx +69 -0
  43. package/components/dashboard/InlineMetricHelpTrigger.tsx +102 -0
  44. package/components/dashboard/IssuePickerModal.tsx +168 -0
  45. package/components/dashboard/KronoFocusPanel.tsx +834 -0
  46. package/components/dashboard/KronosysDatetimePopoverField.tsx +357 -0
  47. package/components/dashboard/KronosysTimePopoverField.tsx +233 -0
  48. package/components/dashboard/LanguageMenu.tsx +123 -0
  49. package/components/dashboard/MongoMirrorSyncLine.tsx +57 -0
  50. package/components/dashboard/NewSessionScopeModal.tsx +410 -0
  51. package/components/dashboard/PageRefreshButton.tsx +130 -0
  52. package/components/dashboard/PlainHelpPopover.tsx +97 -0
  53. package/components/dashboard/ReportingPageToc.tsx +68 -0
  54. package/components/dashboard/ReportingTour.tsx +342 -0
  55. package/components/dashboard/SavedProjectPicker.tsx +92 -0
  56. package/components/dashboard/SavedTagPicker.tsx +115 -0
  57. package/components/dashboard/ScrollToTopFab.tsx +41 -0
  58. package/components/dashboard/SelectedSessionSidebarBlock.tsx +630 -0
  59. package/components/dashboard/SessionEndReasonEditor.tsx +114 -0
  60. package/components/dashboard/SessionListPanel.tsx +320 -0
  61. package/components/dashboard/SessionLocMetricsSection.tsx +128 -0
  62. package/components/dashboard/SettingsTagsProjectsSection.tsx +993 -0
  63. package/components/dashboard/SettingsTour.tsx +332 -0
  64. package/components/dashboard/TagPills.tsx +149 -0
  65. package/components/dashboard/TagsHelpTrigger.tsx +84 -0
  66. package/components/dashboard/TaskFocusPanel.tsx +1261 -0
  67. package/components/dashboard/TaskSessionLiveCard.tsx +832 -0
  68. package/components/dashboard/TaskSubtasksBlock.tsx +748 -0
  69. package/components/dashboard/ThemeToggle.test.tsx +26 -0
  70. package/components/dashboard/ThemeToggle.tsx +36 -0
  71. package/components/dashboard/UserGuideBodyText.tsx +62 -0
  72. package/components/dashboard/WorkspaceGitRepoCard.tsx +191 -0
  73. package/components/dashboard/taskFieldStyles.ts +139 -0
  74. package/components/dashboard/useAnchoredFloatingPortalStyle.ts +71 -0
  75. package/components/dashboard/useDescriptionPopoverAfterMs.ts +220 -0
  76. package/components/dashboard/useKronoFocusLiveSeconds.ts +36 -0
  77. package/components/dashboard/useSmoothStopwatchMs.ts +25 -0
  78. package/lib/appShellHeaderClasses.ts +12 -0
  79. package/lib/backupCsvExport.test.ts +149 -0
  80. package/lib/backupCsvExport.ts +392 -0
  81. package/lib/changelogCopy.ts +34 -0
  82. package/lib/concurrentTaskStartPreference.ts +29 -0
  83. package/lib/dashboardClockFormat.ts +13 -0
  84. package/lib/dashboardColumnChrome.ts +3 -0
  85. package/lib/dashboardColumnHintsStorage.ts +57 -0
  86. package/lib/dashboardCopy.ts +1831 -0
  87. package/lib/dashboardDetachedUrlHintStorage.ts +24 -0
  88. package/lib/dashboardGitIdentityBannerStorage.ts +36 -0
  89. package/lib/dashboardLangStorage.ts +72 -0
  90. package/lib/dashboardQuickSearch.ts +476 -0
  91. package/lib/dashboardQuickSearchQuery.test.ts +63 -0
  92. package/lib/dashboardQuickSearchQuery.ts +179 -0
  93. package/lib/dashboardSessionNav.ts +33 -0
  94. package/lib/dashboardShortcuts.ts +268 -0
  95. package/lib/dashboardTimeZone.ts +91 -0
  96. package/lib/dashboardTourStorage.ts +68 -0
  97. package/lib/dataDir.test.ts +87 -0
  98. package/lib/dataDir.ts +83 -0
  99. package/lib/devDataPreferenceFile.ts +55 -0
  100. package/lib/devDataRuntimeInfo.ts +34 -0
  101. package/lib/formatIsoShort.test.ts +46 -0
  102. package/lib/formatIsoShort.ts +29 -0
  103. package/lib/generatedUserChangelog.ts +34 -0
  104. package/lib/gitlabIssueSearch.ts +8 -0
  105. package/lib/kronoFocusDurationHistory.ts +71 -0
  106. package/lib/kronoFocusRhythm.test.ts +130 -0
  107. package/lib/kronoFocusRhythm.ts +46 -0
  108. package/lib/kronoFocusTimerUrgency.test.ts +74 -0
  109. package/lib/kronoFocusTimerUrgency.ts +24 -0
  110. package/lib/kronosysApi.ts +143 -0
  111. package/lib/legacyEditorPayloadKeys.ts +52 -0
  112. package/lib/legacyKronoFocusStorageKeys.test.ts +29 -0
  113. package/lib/legacyKronoFocusStorageKeys.ts +32 -0
  114. package/lib/licensesCopy.ts +128 -0
  115. package/lib/openPlainTextInNewTab.ts +49 -0
  116. package/lib/readKronosysPackageVersion.ts +10 -0
  117. package/lib/reportingAggregate.test.ts +325 -0
  118. package/lib/reportingAggregate.ts +819 -0
  119. package/lib/reportingDatePresets.ts +41 -0
  120. package/lib/reportingMetricHelp.ts +430 -0
  121. package/lib/reportingNonFinalIndicators.test.ts +157 -0
  122. package/lib/reportingNonFinalIndicators.ts +102 -0
  123. package/lib/reportingStrings.ts +491 -0
  124. package/lib/reportingTagWeekBreakdown.test.ts +141 -0
  125. package/lib/reportingTagWeekBreakdown.ts +181 -0
  126. package/lib/reportingWeekLayout.test.ts +239 -0
  127. package/lib/reportingWeekLayout.ts +313 -0
  128. package/lib/sessionAssiduity.test.ts +25 -0
  129. package/lib/sessionAssiduity.ts +33 -0
  130. package/lib/sessionEndReason.ts +55 -0
  131. package/lib/sessionEndWarnings.test.ts +200 -0
  132. package/lib/sessionEndWarnings.ts +125 -0
  133. package/lib/sessionListMerge.test.ts +101 -0
  134. package/lib/sessionListMerge.ts +70 -0
  135. package/lib/sessionTaskSidebarStats.test.ts +24 -0
  136. package/lib/sessionTaskSidebarStats.ts +54 -0
  137. package/lib/settingsCopy.ts +1276 -0
  138. package/lib/taskParsing.test.ts +153 -0
  139. package/lib/taskParsing.ts +737 -0
  140. package/lib/theme.ts +15 -0
  141. package/lib/translucentButtonClasses.ts +34 -0
  142. package/lib/usageProfile.test.ts +84 -0
  143. package/lib/usageProfile.ts +52 -0
  144. package/lib/userGuideCopy.ts +464 -0
  145. package/lib/workspaceLocDefaults.ts +21 -0
  146. package/next-env.d.ts +6 -0
  147. package/next.config.ts +15 -0
  148. package/package.json +87 -0
  149. package/postcss.config.mjs +12 -0
  150. package/public/apple-icon.png +0 -0
  151. package/public/favicon.ico +0 -0
  152. package/public/file.svg +1 -0
  153. package/public/globe.svg +1 -0
  154. package/public/icon-192.png +0 -0
  155. package/public/icon-512.png +0 -0
  156. package/public/icon.png +0 -0
  157. package/public/next.svg +1 -0
  158. package/public/sw.js +13 -0
  159. package/public/traceback.png +0 -0
  160. package/public/vercel.svg +1 -0
  161. package/public/window.svg +1 -0
  162. package/server/actionDispatch.test.ts +723 -0
  163. package/server/actionDispatch.ts +1476 -0
  164. package/server/actionTaskSession.test.ts +713 -0
  165. package/server/actionTaskSession.ts +717 -0
  166. package/server/db.ts +42 -0
  167. package/server/defaultCfg.ts +87 -0
  168. package/server/gitlabTokenStore.ts +34 -0
  169. package/server/kronoFocusHydrate.test.ts +142 -0
  170. package/server/kronoFocusHydrate.ts +69 -0
  171. package/server/kronoFocusMigrate.test.ts +53 -0
  172. package/server/kronoFocusMigrate.ts +78 -0
  173. package/server/mainTimerHydrate.test.ts +65 -0
  174. package/server/mainTimerHydrate.ts +53 -0
  175. package/server/payloadStore.test.ts +78 -0
  176. package/server/payloadStore.ts +83 -0
  177. package/server/sessionWallHydrate.test.ts +46 -0
  178. package/server/sessionWallHydrate.ts +88 -0
  179. package/tsconfig.json +41 -0
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * kronosys CLI — entry point for `npx kronosys <command>`
4
+ *
5
+ * Supported commands:
6
+ * start → next start -p 5555 (production)
7
+ * dev → next dev --webpack -H kronosys -p 5555
8
+ */
9
+
10
+ import { spawn } from "node:child_process";
11
+ import { fileURLToPath } from "node:url";
12
+ import { dirname, resolve } from "node:path";
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const projectRoot = resolve(__dirname, "..");
16
+
17
+ const [, , command = "start", ...rest] = process.argv;
18
+
19
+ const commands = {
20
+ start: ["npx", "next", "start", "-p", "5555", ...rest],
21
+ dev: ["npx", "next", "dev", "--webpack", "-H", "kronosys", "-p", "5555", ...rest],
22
+ };
23
+
24
+ if (!commands[command]) {
25
+ console.error(`[kronosys] Unknown command: "${command}"`);
26
+ console.error(` Available: ${Object.keys(commands).join(", ")}`);
27
+ process.exit(1);
28
+ }
29
+
30
+ const [bin, ...args] = commands[command];
31
+
32
+ const child = spawn(bin, args, {
33
+ cwd: projectRoot,
34
+ stdio: "inherit",
35
+ shell: false,
36
+ env: { ...process.env, NODE_ENV: command === "dev" ? "development" : "production" },
37
+ });
38
+
39
+ child.on("error", (err) => {
40
+ console.error(`[kronosys] Failed to start: ${err.message}`);
41
+ process.exit(1);
42
+ });
43
+
44
+ child.on("exit", (code) => {
45
+ process.exit(code ?? 0);
46
+ });
@@ -0,0 +1,19 @@
1
+ "use client";
2
+
3
+ import { createContext, useContext, type ReactNode } from "react";
4
+
5
+ const KronosysPackageVersionContext = createContext<string>("0.0.0");
6
+
7
+ export function KronosysPackageVersionProvider({
8
+ version,
9
+ children,
10
+ }: {
11
+ version: string;
12
+ children: ReactNode;
13
+ }) {
14
+ return <KronosysPackageVersionContext.Provider value={version}>{children}</KronosysPackageVersionContext.Provider>;
15
+ }
16
+
17
+ export function useKronosysPackageVersion(): string {
18
+ return useContext(KronosysPackageVersionContext);
19
+ }
@@ -0,0 +1,109 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ type ReactNode,
12
+ } from "react";
13
+ import { useRouter } from "next/navigation";
14
+
15
+ import { fetchKronosysState, type KronosysUpdatePayload } from "@/lib/kronosysApi";
16
+
17
+ export type KronosysPayloadContextValue = {
18
+ payload: KronosysUpdatePayload | null;
19
+ error: string | null;
20
+ /**
21
+ * Recharge l’état depuis `/api/state` et met à jour le contexte partagé (toutes les routes).
22
+ * Met à jour `latestPayloadRef` de façon synchrone pour les appelants qui doivent lire le
23
+ * résultat avant le prochain rendu React.
24
+ */
25
+ refresh: (options?: { routerInvalidate?: boolean }) => Promise<boolean>;
26
+ /** Dernier payload connu (synchrone, même immédiatement après `await refresh()`). */
27
+ getLatestPayload: () => KronosysUpdatePayload | null;
28
+ };
29
+
30
+ const KronosysPayloadContext = createContext<KronosysPayloadContextValue | null>(null);
31
+
32
+ export function KronosysPayloadProvider({ children }: { children: ReactNode }) {
33
+ const router = useRouter();
34
+ const [payload, setPayload] = useState<KronosysUpdatePayload | null>(null);
35
+ const [error, setError] = useState<string | null>(null);
36
+ const lastRefreshOkRef = useRef(true);
37
+ const latestPayloadRef = useRef<KronosysUpdatePayload | null>(null);
38
+
39
+ const refresh = useCallback(
40
+ async (options?: { routerInvalidate?: boolean }): Promise<boolean> => {
41
+ try {
42
+ const data = await fetchKronosysState("dashboard");
43
+ if (data.type === "updateData" && data.payload) {
44
+ const p = data.payload as KronosysUpdatePayload;
45
+ latestPayloadRef.current = p;
46
+ setPayload(p);
47
+ setError(null);
48
+ }
49
+ lastRefreshOkRef.current = true;
50
+ if (options?.routerInvalidate === true) {
51
+ router.refresh();
52
+ }
53
+ return true;
54
+ } catch (e: unknown) {
55
+ lastRefreshOkRef.current = false;
56
+ setError(e instanceof Error ? e.message : String(e));
57
+ return false;
58
+ }
59
+ },
60
+ [router],
61
+ );
62
+
63
+ const getLatestPayload = useCallback(() => latestPayloadRef.current, []);
64
+
65
+ useEffect(() => {
66
+ let cancelled = false;
67
+ let timer: ReturnType<typeof setTimeout> | null = null;
68
+ let failures = 0;
69
+
70
+ const pollState = async () => {
71
+ await refresh();
72
+ if (cancelled) {
73
+ return;
74
+ }
75
+ const ok = lastRefreshOkRef.current;
76
+ failures = ok ? 0 : Math.min(failures + 1, 8);
77
+ const delay = ok ? 2000 : Math.min(30000, 2000 * 2 ** failures);
78
+ timer = setTimeout(() => void pollState(), delay);
79
+ };
80
+
81
+ void pollState();
82
+ return () => {
83
+ cancelled = true;
84
+ if (timer) {
85
+ clearTimeout(timer);
86
+ }
87
+ };
88
+ }, [refresh]);
89
+
90
+ const value = useMemo(
91
+ (): KronosysPayloadContextValue => ({
92
+ payload,
93
+ error,
94
+ refresh,
95
+ getLatestPayload,
96
+ }),
97
+ [payload, error, refresh, getLatestPayload],
98
+ );
99
+
100
+ return <KronosysPayloadContext.Provider value={value}>{children}</KronosysPayloadContext.Provider>;
101
+ }
102
+
103
+ export function useKronosysPayload(): KronosysPayloadContextValue {
104
+ const v = useContext(KronosysPayloadContext);
105
+ if (!v) {
106
+ throw new Error("useKronosysPayload doit être utilisé sous KronosysPayloadProvider.");
107
+ }
108
+ return v;
109
+ }
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+
5
+ /**
6
+ * Enregistre le service worker en production pour satisfaire les critères d’installation PWA
7
+ * (Chrome, Edge, Brave, etc.). En `next dev`, on évite le SW pour ne pas masquer le HMR.
8
+ */
9
+ export function PwaRegister() {
10
+ useEffect(() => {
11
+ if (process.env.NODE_ENV !== "production") return;
12
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return;
13
+
14
+ const register = () => {
15
+ void navigator.serviceWorker.register("/sw.js", { scope: "/" }).catch(() => {
16
+ /* ignore — localhost sans build, permissions, etc. */
17
+ });
18
+ };
19
+
20
+ if (document.readyState === "complete") register();
21
+ else window.addEventListener("load", register, { once: true });
22
+ }, []);
23
+
24
+ return null;
25
+ }
@@ -0,0 +1,21 @@
1
+ import Link from "next/link";
2
+
3
+ /** Pied de page global : lien vers les licences (toutes les pages du dashboard). */
4
+ export function SiteLegalFooter() {
5
+ return (
6
+ <footer className="border-t border-zinc-200 bg-zinc-50 px-4 py-3 text-center text-[0.7rem] leading-relaxed text-zinc-600 dark:border-zinc-800/80 dark:bg-zinc-900 dark:text-zinc-500 sm:py-4 sm:text-xs">
7
+ <p>
8
+ <Link
9
+ href="/licenses"
10
+ className="text-zinc-600 underline-offset-2 hover:text-violet-700 dark:text-zinc-400 dark:hover:text-violet-300 hover:underline"
11
+ >
12
+ Licences et mentions · Licenses
13
+ </Link>
14
+ <span className="text-zinc-400 dark:text-zinc-600" aria-hidden>
15
+ {" · "}
16
+ </span>
17
+ <span>Kronosys — MIT License</span>
18
+ </p>
19
+ </footer>
20
+ );
21
+ }
@@ -0,0 +1,78 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useLayoutEffect,
9
+ useState,
10
+ type ReactNode,
11
+ } from "react";
12
+ import { THEME_STORAGE_KEY, type KronosysTheme } from "@/lib/theme";
13
+
14
+ type Ctx = {
15
+ theme: KronosysTheme;
16
+ setTheme: (t: KronosysTheme) => void;
17
+ toggleTheme: () => void;
18
+ mounted: boolean;
19
+ };
20
+
21
+ const ThemeContext = createContext<Ctx | null>(null);
22
+
23
+ export function ThemeProvider({ children }: { children: ReactNode }) {
24
+ /** Même valeur SSR et premier rendu client que `<html>` sans classe `dark` (layout), pour éviter les erreurs d’hydratation. Le script `getThemeBootstrapScript` + ce layout alignent le DOM avant peinture. */
25
+ const [theme, setThemeState] = useState<KronosysTheme>("light");
26
+ const [mounted, setMounted] = useState(false);
27
+
28
+ useLayoutEffect(() => {
29
+ if (typeof document === "undefined") {
30
+ return;
31
+ }
32
+ setThemeState(document.documentElement.classList.contains("dark") ? "dark" : "light");
33
+ }, []);
34
+
35
+ useEffect(() => {
36
+ const mountedTimer = window.setTimeout(() => setMounted(true), 0);
37
+ return () => window.clearTimeout(mountedTimer);
38
+ }, []);
39
+
40
+ useEffect(() => {
41
+ if (!mounted || typeof document === "undefined") {
42
+ return;
43
+ }
44
+ const root = document.documentElement;
45
+ if (theme === "dark") {
46
+ root.classList.add("dark");
47
+ } else {
48
+ root.classList.remove("dark");
49
+ }
50
+ try {
51
+ localStorage.setItem(THEME_STORAGE_KEY, theme);
52
+ } catch {
53
+ /* ignore */
54
+ }
55
+ }, [theme, mounted]);
56
+
57
+ const setTheme = useCallback((t: KronosysTheme) => {
58
+ setThemeState(t);
59
+ }, []);
60
+
61
+ const toggleTheme = useCallback(() => {
62
+ setThemeState((prev) => (prev === "dark" ? "light" : "dark"));
63
+ }, []);
64
+
65
+ return (
66
+ <ThemeContext.Provider value={{ theme, setTheme, toggleTheme, mounted }}>
67
+ {children}
68
+ </ThemeContext.Provider>
69
+ );
70
+ }
71
+
72
+ export function useKronosysTheme(): Ctx {
73
+ const ctx = useContext(ThemeContext);
74
+ if (!ctx) {
75
+ throw new Error("useKronosysTheme must be used within ThemeProvider");
76
+ }
77
+ return ctx;
78
+ }