@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.
- package/README.md +81 -0
- package/app/api/action/route.ts +16 -0
- package/app/api/backup/route.ts +84 -0
- package/app/api/health/route.ts +22 -0
- package/app/api/state/route.ts +27 -0
- package/app/apple-icon.png +0 -0
- package/app/changelog/page.tsx +122 -0
- package/app/globals.css +210 -0
- package/app/guide/layout.tsx +11 -0
- package/app/guide/page.tsx +278 -0
- package/app/icon.png +0 -0
- package/app/layout.tsx +77 -0
- package/app/licenses/layout.tsx +11 -0
- package/app/licenses/page.tsx +246 -0
- package/app/manifest.ts +32 -0
- package/app/page.tsx +1610 -0
- package/app/reporting/page.tsx +2943 -0
- package/app/settings/layout.tsx +10 -0
- package/app/settings/page.tsx +3518 -0
- package/bin/kronosys.mjs +46 -0
- package/components/KronosysPackageVersionProvider.tsx +19 -0
- package/components/KronosysPayloadProvider.tsx +109 -0
- package/components/PwaRegister.tsx +25 -0
- package/components/SiteLegalFooter.tsx +21 -0
- package/components/ThemeProvider.tsx +78 -0
- package/components/dashboard/AppShellLiveSessionDrawer.tsx +394 -0
- package/components/dashboard/AppShellRouteNav.tsx +131 -0
- package/components/dashboard/AppVersionStamp.tsx +16 -0
- package/components/dashboard/DashboardCollapsibleSection.tsx +57 -0
- package/components/dashboard/DashboardColumnHintsBanner.tsx +159 -0
- package/components/dashboard/DashboardCommandCenter.tsx +470 -0
- package/components/dashboard/DashboardLangGateModal.tsx +118 -0
- package/components/dashboard/DashboardLoadingOverlay.tsx +42 -0
- package/components/dashboard/DashboardSimpleModal.tsx +337 -0
- package/components/dashboard/DashboardSuspenseFallback.tsx +52 -0
- package/components/dashboard/DashboardToastProvider.tsx +64 -0
- package/components/dashboard/DashboardTour.tsx +435 -0
- package/components/dashboard/DeferredDescriptionPopoverWrap.tsx +39 -0
- package/components/dashboard/DeleteSessionModal.tsx +130 -0
- package/components/dashboard/DescriptionTooltipPortaled.tsx +31 -0
- package/components/dashboard/GitIdentityQuickSetupModal.tsx +211 -0
- package/components/dashboard/HeaderIntegrationBadges.tsx +69 -0
- package/components/dashboard/InlineMetricHelpTrigger.tsx +102 -0
- package/components/dashboard/IssuePickerModal.tsx +168 -0
- package/components/dashboard/KronoFocusPanel.tsx +834 -0
- package/components/dashboard/KronosysDatetimePopoverField.tsx +357 -0
- package/components/dashboard/KronosysTimePopoverField.tsx +233 -0
- package/components/dashboard/LanguageMenu.tsx +123 -0
- package/components/dashboard/MongoMirrorSyncLine.tsx +57 -0
- package/components/dashboard/NewSessionScopeModal.tsx +410 -0
- package/components/dashboard/PageRefreshButton.tsx +130 -0
- package/components/dashboard/PlainHelpPopover.tsx +97 -0
- package/components/dashboard/ReportingPageToc.tsx +68 -0
- package/components/dashboard/ReportingTour.tsx +342 -0
- package/components/dashboard/SavedProjectPicker.tsx +92 -0
- package/components/dashboard/SavedTagPicker.tsx +115 -0
- package/components/dashboard/ScrollToTopFab.tsx +41 -0
- package/components/dashboard/SelectedSessionSidebarBlock.tsx +630 -0
- package/components/dashboard/SessionEndReasonEditor.tsx +114 -0
- package/components/dashboard/SessionListPanel.tsx +320 -0
- package/components/dashboard/SessionLocMetricsSection.tsx +128 -0
- package/components/dashboard/SettingsTagsProjectsSection.tsx +993 -0
- package/components/dashboard/SettingsTour.tsx +332 -0
- package/components/dashboard/TagPills.tsx +149 -0
- package/components/dashboard/TagsHelpTrigger.tsx +84 -0
- package/components/dashboard/TaskFocusPanel.tsx +1261 -0
- package/components/dashboard/TaskSessionLiveCard.tsx +832 -0
- package/components/dashboard/TaskSubtasksBlock.tsx +748 -0
- package/components/dashboard/ThemeToggle.test.tsx +26 -0
- package/components/dashboard/ThemeToggle.tsx +36 -0
- package/components/dashboard/UserGuideBodyText.tsx +62 -0
- package/components/dashboard/WorkspaceGitRepoCard.tsx +191 -0
- package/components/dashboard/taskFieldStyles.ts +139 -0
- package/components/dashboard/useAnchoredFloatingPortalStyle.ts +71 -0
- package/components/dashboard/useDescriptionPopoverAfterMs.ts +220 -0
- package/components/dashboard/useKronoFocusLiveSeconds.ts +36 -0
- package/components/dashboard/useSmoothStopwatchMs.ts +25 -0
- package/lib/appShellHeaderClasses.ts +12 -0
- package/lib/backupCsvExport.test.ts +149 -0
- package/lib/backupCsvExport.ts +392 -0
- package/lib/changelogCopy.ts +34 -0
- package/lib/concurrentTaskStartPreference.ts +29 -0
- package/lib/dashboardClockFormat.ts +13 -0
- package/lib/dashboardColumnChrome.ts +3 -0
- package/lib/dashboardColumnHintsStorage.ts +57 -0
- package/lib/dashboardCopy.ts +1831 -0
- package/lib/dashboardDetachedUrlHintStorage.ts +24 -0
- package/lib/dashboardGitIdentityBannerStorage.ts +36 -0
- package/lib/dashboardLangStorage.ts +72 -0
- package/lib/dashboardQuickSearch.ts +476 -0
- package/lib/dashboardQuickSearchQuery.test.ts +63 -0
- package/lib/dashboardQuickSearchQuery.ts +179 -0
- package/lib/dashboardSessionNav.ts +33 -0
- package/lib/dashboardShortcuts.ts +268 -0
- package/lib/dashboardTimeZone.ts +91 -0
- package/lib/dashboardTourStorage.ts +68 -0
- package/lib/dataDir.test.ts +87 -0
- package/lib/dataDir.ts +83 -0
- package/lib/devDataPreferenceFile.ts +55 -0
- package/lib/devDataRuntimeInfo.ts +34 -0
- package/lib/formatIsoShort.test.ts +46 -0
- package/lib/formatIsoShort.ts +29 -0
- package/lib/generatedUserChangelog.ts +34 -0
- package/lib/gitlabIssueSearch.ts +8 -0
- package/lib/kronoFocusDurationHistory.ts +71 -0
- package/lib/kronoFocusRhythm.test.ts +130 -0
- package/lib/kronoFocusRhythm.ts +46 -0
- package/lib/kronoFocusTimerUrgency.test.ts +74 -0
- package/lib/kronoFocusTimerUrgency.ts +24 -0
- package/lib/kronosysApi.ts +143 -0
- package/lib/legacyEditorPayloadKeys.ts +52 -0
- package/lib/legacyKronoFocusStorageKeys.test.ts +29 -0
- package/lib/legacyKronoFocusStorageKeys.ts +32 -0
- package/lib/licensesCopy.ts +128 -0
- package/lib/openPlainTextInNewTab.ts +49 -0
- package/lib/readKronosysPackageVersion.ts +10 -0
- package/lib/reportingAggregate.test.ts +325 -0
- package/lib/reportingAggregate.ts +819 -0
- package/lib/reportingDatePresets.ts +41 -0
- package/lib/reportingMetricHelp.ts +430 -0
- package/lib/reportingNonFinalIndicators.test.ts +157 -0
- package/lib/reportingNonFinalIndicators.ts +102 -0
- package/lib/reportingStrings.ts +491 -0
- package/lib/reportingTagWeekBreakdown.test.ts +141 -0
- package/lib/reportingTagWeekBreakdown.ts +181 -0
- package/lib/reportingWeekLayout.test.ts +239 -0
- package/lib/reportingWeekLayout.ts +313 -0
- package/lib/sessionAssiduity.test.ts +25 -0
- package/lib/sessionAssiduity.ts +33 -0
- package/lib/sessionEndReason.ts +55 -0
- package/lib/sessionEndWarnings.test.ts +200 -0
- package/lib/sessionEndWarnings.ts +125 -0
- package/lib/sessionListMerge.test.ts +101 -0
- package/lib/sessionListMerge.ts +70 -0
- package/lib/sessionTaskSidebarStats.test.ts +24 -0
- package/lib/sessionTaskSidebarStats.ts +54 -0
- package/lib/settingsCopy.ts +1276 -0
- package/lib/taskParsing.test.ts +153 -0
- package/lib/taskParsing.ts +737 -0
- package/lib/theme.ts +15 -0
- package/lib/translucentButtonClasses.ts +34 -0
- package/lib/usageProfile.test.ts +84 -0
- package/lib/usageProfile.ts +52 -0
- package/lib/userGuideCopy.ts +464 -0
- package/lib/workspaceLocDefaults.ts +21 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +15 -0
- package/package.json +87 -0
- package/postcss.config.mjs +12 -0
- package/public/apple-icon.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/icon.png +0 -0
- package/public/next.svg +1 -0
- package/public/sw.js +13 -0
- package/public/traceback.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/server/actionDispatch.test.ts +723 -0
- package/server/actionDispatch.ts +1476 -0
- package/server/actionTaskSession.test.ts +713 -0
- package/server/actionTaskSession.ts +717 -0
- package/server/db.ts +42 -0
- package/server/defaultCfg.ts +87 -0
- package/server/gitlabTokenStore.ts +34 -0
- package/server/kronoFocusHydrate.test.ts +142 -0
- package/server/kronoFocusHydrate.ts +69 -0
- package/server/kronoFocusMigrate.test.ts +53 -0
- package/server/kronoFocusMigrate.ts +78 -0
- package/server/mainTimerHydrate.test.ts +65 -0
- package/server/mainTimerHydrate.ts +53 -0
- package/server/payloadStore.test.ts +78 -0
- package/server/payloadStore.ts +83 -0
- package/server/sessionWallHydrate.test.ts +46 -0
- package/server/sessionWallHydrate.ts +88 -0
- package/tsconfig.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Kronosys — application Next.js
|
|
2
|
+
|
|
3
|
+
Stack : **Next 16** (App Router), **React 19**, **Tailwind 4**, **better-sqlite3** (SQLite côté serveur Node), **mongodb** (optionnel).
|
|
4
|
+
|
|
5
|
+
**Guide utilisateur** : dans l’app, **`/guide`** (icône *Livre* à côté de Rapports / Paramètres) — mêmes thèmes, navigation, recherche de rubriques. Dépôt : [`docs/USER_GUIDE.md`](docs/USER_GUIDE.md) (aligné) — règle projet [`.cursor/rules/kronosys-user-guide.mdc`](.cursor/rules/kronosys-user-guide.mdc).
|
|
6
|
+
|
|
7
|
+
## Scripts
|
|
8
|
+
|
|
9
|
+
| Commande | Effet |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `npm run dev` | `next dev` sur le port **5555** |
|
|
12
|
+
| `npm run build` / `npm run start` | Production |
|
|
13
|
+
| `npm run test` | Vitest |
|
|
14
|
+
| `npm run test:coverage` | Vitest avec couverture LCOV pour SonarQube Cloud |
|
|
15
|
+
| `npm run hooks:install` | Installe Husky et active les hooks Git du dépôt |
|
|
16
|
+
| `npm run release:dry` | Simule bump semver + `CHANGELOG.md` + tag (aucune écriture) |
|
|
17
|
+
| `npm run release` | Bump `package.json` / `package-lock.json`, met à jour le journal, commit, tag `v…` |
|
|
18
|
+
| `npm run release:first` | Une fois : journal complet pour la version courante **sans** bump (puis tag `v…`) |
|
|
19
|
+
|
|
20
|
+
Les messages de commit doivent suivre les [Conventional Commits](https://www.conventionalcommits.org/) (`feat`, `fix`, etc.) pour que le journal regroupe correctement **nouveautés**, **corrections** et autres rubriques. Après `npm run release`, pousser avec `git push --follow-tags`. Si le dépôt n’a pas encore de tag pour la dernière entrée du `CHANGELOG.md`, créez `git tag vX.Y.Z <commit>` avant le prochain `release`, sinon l’historique serait dupliqué (voir l’en-tête du journal). Sur **GitHub**, le workflow **Release** crée une **GitHub Release** à chaque tag `v*.*.*`. Sur **GitLab**, créez une release à partir du même tag (interface ou `glab release create`) en réutilisant la section extraite : `bash scripts/extract-changelog-for-tag.sh CHANGELOG.md X.Y.Z`.
|
|
21
|
+
|
|
22
|
+
Depuis la **racine** du monorepo : `npm run dev` délègue ici.
|
|
23
|
+
|
|
24
|
+
Le projet déclare ses versions Node / npm avec **Volta** (`package.json` → `volta`) afin d’obtenir le même outillage localement et en automatisation.
|
|
25
|
+
|
|
26
|
+
## CI/CD GitLab et SonarQube Cloud
|
|
27
|
+
|
|
28
|
+
Le pipeline GitLab (`.gitlab-ci.yml`) exécute lint, audit SCA minimal avec `npm audit`, tests Vitest avec couverture, E2E Playwright, build Next, puis l’analyse **SonarQube Cloud**. La clé projet et l’organisation sont versionnées dans `sonar-project.properties`; le token Sonar ne doit jamais être commité.
|
|
29
|
+
|
|
30
|
+
L’audit `npm audit --audit-level=moderate` est non bloquant au départ afin de garder le signal visible pendant le traitement des vulnérabilités transitives déjà présentes.
|
|
31
|
+
|
|
32
|
+
- `SONAR_TOKEN` : token SonarQube Cloud.
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
- `GET /api/health` — vérifie l’accès SQLite
|
|
37
|
+
- `GET /api/state` — stub ; à remplacer par l’état complet (sessions, tâches, …)
|
|
38
|
+
|
|
39
|
+
## Variables d’environnement
|
|
40
|
+
|
|
41
|
+
- **`TRACE_DATA_DIR`** — répertoire des données (défaut : emplacement utilisateur type XDG / macOS / Windows). Si elle est définie, elle prime sur toute autre règle (y compris l’isolation dev / prod).
|
|
42
|
+
- **`KRONOSYS_DEV_USE_PROD_DATA`** — si non vide / `1` / `true` / `on` / `yes`, le serveur lancé en **`NODE_ENV=development`** (`next dev`) utilise le même segment de chemin que la production (`v4`) au lieu du répertoire isolé `v4-dev`. Raccourci côté script ; l’équivalent se configure dans l’app : **Paramètres → Général** (bloc *Mode développement*), enregistrement avec **Enregistrer** (un petit fichier sur le disque sert aussi au prochain démarrage).
|
|
43
|
+
|
|
44
|
+
## Installation (`npm install`)
|
|
45
|
+
|
|
46
|
+
Le paquet **n’est pas « cassé »** : l’installation peut échouer si **l’environnement** ne remplit pas les conditions ci‑dessous (souvent **plusieurs** à la fois, Linux comme Windows).
|
|
47
|
+
|
|
48
|
+
### 1. Module natif `better-sqlite3`
|
|
49
|
+
|
|
50
|
+
SQLite passe par **`better-sqlite3`** (binaire précompilé ou compilation `node-gyp`).
|
|
51
|
+
|
|
52
|
+
- **Node** : préférer une **LTS x64** (**20 ou 22**), voir `engines` dans `package.json`.
|
|
53
|
+
- **Windows** : outils natifs Node (assistant d’installation) et, si compilation : **Visual Studio 2022** ou *Build Tools* avec **Développement Desktop en C++**.
|
|
54
|
+
- **Linux / WSL** : si compilation, paquets type `build-essential` / chaîne de build usuelle pour les modules natifs.
|
|
55
|
+
|
|
56
|
+
### 2. TLS (proxy d’entreprise, inspection HTTPS)
|
|
57
|
+
|
|
58
|
+
Si le log parle de **`SELF_SIGNED_CERT_IN_CHAIN`** ou d’échec de téléchargement (**prebuild**, **headers** `nodejs.org`), ce n’est pas un défaut du paquet Kronosys : Node n’accepte pas la chaîne de certificats imposée par l’environnement.
|
|
59
|
+
|
|
60
|
+
**Piste simple** (Node récent, magasin de certificats du système) :
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
export NODE_OPTIONS='--use-system-ca'
|
|
64
|
+
npm install
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Sinon : `NODE_EXTRA_CA_CERTS` pointant vers le PEM racine de l’organisation, ou `npm config set cafile …` (voir documentation interne).
|
|
68
|
+
|
|
69
|
+
### 3. Où lancer `npm install`
|
|
70
|
+
|
|
71
|
+
À la racine du dépôt **`apps/kronosys`**, ou dans le dossier décompressé du ZIP qui contient **`package.json`** (et éventuellement le `.tgz`), pas en mélangeant une install partielle à la main d’une seule dépendance.
|
|
72
|
+
|
|
73
|
+
## Publication npm (`@nightkatana/kronosys-app`)
|
|
74
|
+
|
|
75
|
+
La version est définie dans `package.json` (actuellement **0.1.0**). Avant la première publication : créer le scope **@nightkatana** sur [npmjs.com](https://www.npmjs.com/) si besoin, puis `npm login`. Depuis ce dossier :
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm publish --access public
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Le script `prepublishOnly` lance `npm run build`. Pour inspecter le contenu du paquet : `npm pack --dry-run`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
|
|
3
|
+
import { dispatchKronosysAction } from "@/server/actionDispatch";
|
|
4
|
+
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
export async function POST(req: Request) {
|
|
8
|
+
let body: Record<string, unknown> = {};
|
|
9
|
+
try {
|
|
10
|
+
body = (await req.json()) as Record<string, unknown>;
|
|
11
|
+
} catch {
|
|
12
|
+
return NextResponse.json({ ok: false, error: "Invalid JSON" }, { status: 400 });
|
|
13
|
+
}
|
|
14
|
+
const out = await dispatchKronosysAction(body);
|
|
15
|
+
return NextResponse.json({ ok: out.ok, result: out.result ?? {} });
|
|
16
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
buildCsvBackupPart,
|
|
7
|
+
buildCsvBackupZipSync,
|
|
8
|
+
csvBackupFilename,
|
|
9
|
+
csvBackupZipFilename,
|
|
10
|
+
isCsvBackupTable,
|
|
11
|
+
} from "@/lib/backupCsvExport";
|
|
12
|
+
import { ensureDataDirectory } from "@/lib/dataDir";
|
|
13
|
+
import { getSqlite } from "@/server/db";
|
|
14
|
+
import { readPayload } from "@/server/payloadStore";
|
|
15
|
+
|
|
16
|
+
export const runtime = "nodejs";
|
|
17
|
+
|
|
18
|
+
const DB_NAME = "kronosys.sqlite";
|
|
19
|
+
|
|
20
|
+
function fileStamp(): string {
|
|
21
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z").replace(/:/g, "-");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function GET(req: Request) {
|
|
25
|
+
const format = new URL(req.url).searchParams.get("format") ?? "json";
|
|
26
|
+
const stamp = fileStamp();
|
|
27
|
+
|
|
28
|
+
if (format === "sqlite") {
|
|
29
|
+
try {
|
|
30
|
+
getSqlite().pragma("wal_checkpoint(TRUNCATE)");
|
|
31
|
+
} catch {
|
|
32
|
+
/* ignore */
|
|
33
|
+
}
|
|
34
|
+
const filePath = path.join(ensureDataDirectory(), DB_NAME);
|
|
35
|
+
if (!fs.existsSync(filePath)) {
|
|
36
|
+
return NextResponse.json({ ok: false, error: "sqlite_missing" }, { status: 404 });
|
|
37
|
+
}
|
|
38
|
+
const buf = fs.readFileSync(filePath);
|
|
39
|
+
return new NextResponse(buf, {
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/vnd.sqlite3",
|
|
42
|
+
"Content-Disposition": `attachment; filename="kronosys-backup-${stamp}.sqlite"`,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const payload = readPayload();
|
|
48
|
+
|
|
49
|
+
if (format === "csv-zip" || format === "csv_zip") {
|
|
50
|
+
const zipBytes = buildCsvBackupZipSync(payload, stamp);
|
|
51
|
+
return new NextResponse(Buffer.from(zipBytes), {
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/zip",
|
|
54
|
+
"Content-Disposition": `attachment; filename="${csvBackupZipFilename(stamp)}"`,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (format === "csv") {
|
|
60
|
+
const tableRaw = new URL(req.url).searchParams.get("table") ?? "sessions";
|
|
61
|
+
if (!isCsvBackupTable(tableRaw)) {
|
|
62
|
+
return NextResponse.json({ ok: false, error: "unknown_csv_table" }, { status: 400 });
|
|
63
|
+
}
|
|
64
|
+
const body = buildCsvBackupPart(tableRaw, payload);
|
|
65
|
+
return new NextResponse(body, {
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "text/csv; charset=utf-8",
|
|
68
|
+
"Content-Disposition": `attachment; filename="${csvBackupFilename(tableRaw, stamp)}"`,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (format !== "json") {
|
|
74
|
+
return NextResponse.json({ ok: false, error: "unknown_format" }, { status: 400 });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const body = JSON.stringify(payload, null, 2);
|
|
78
|
+
return new NextResponse(body, {
|
|
79
|
+
headers: {
|
|
80
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
81
|
+
"Content-Disposition": `attachment; filename="kronosys-state-${stamp}.json"`,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
|
|
3
|
+
import { readKronosysPackageVersion } from "@/lib/readKronosysPackageVersion";
|
|
4
|
+
import { readPayload } from "@/server/payloadStore";
|
|
5
|
+
import { getSqlite } from "@/server/db";
|
|
6
|
+
|
|
7
|
+
export const runtime = "nodejs";
|
|
8
|
+
|
|
9
|
+
export function GET() {
|
|
10
|
+
try {
|
|
11
|
+
getSqlite().prepare("SELECT 1 as ok").get();
|
|
12
|
+
readPayload();
|
|
13
|
+
return NextResponse.json({
|
|
14
|
+
ok: true,
|
|
15
|
+
service: "kronosys-next",
|
|
16
|
+
version: readKronosysPackageVersion(),
|
|
17
|
+
sqlite: true,
|
|
18
|
+
});
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return NextResponse.json({ ok: false, error: String(e) }, { status: 500 });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
|
|
3
|
+
import { getDevDataRuntimeInfo } from "@/lib/devDataRuntimeInfo";
|
|
4
|
+
import { readPayload } from "@/server/payloadStore";
|
|
5
|
+
|
|
6
|
+
export const runtime = "nodejs";
|
|
7
|
+
|
|
8
|
+
export function GET(req: Request) {
|
|
9
|
+
const url = new URL(req.url);
|
|
10
|
+
const view = url.searchParams.get("view") === "sidebar" ? "sidebar" : "dashboard";
|
|
11
|
+
const payload = readPayload();
|
|
12
|
+
return NextResponse.json(
|
|
13
|
+
{
|
|
14
|
+
type: "updateData",
|
|
15
|
+
payload: {
|
|
16
|
+
...payload,
|
|
17
|
+
viewType: view,
|
|
18
|
+
devDataRuntime: getDevDataRuntimeInfo(),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
headers: {
|
|
23
|
+
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Suspense, useMemo } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { useSearchParams } from "next/navigation";
|
|
6
|
+
import { AppVersionStamp } from "@/components/dashboard/AppVersionStamp";
|
|
7
|
+
import { ThemeToggle } from "@/components/dashboard/ThemeToggle";
|
|
8
|
+
import { PageRefreshButton } from "@/components/dashboard/PageRefreshButton";
|
|
9
|
+
import { ScrollToTopFab } from "@/components/dashboard/ScrollToTopFab";
|
|
10
|
+
import { useKronosysPayload } from "@/components/KronosysPayloadProvider";
|
|
11
|
+
import {
|
|
12
|
+
appShellHeaderClassName,
|
|
13
|
+
appShellHeaderToolRowClassName,
|
|
14
|
+
} from "@/lib/appShellHeaderClasses";
|
|
15
|
+
import { changelogBundle } from "@/lib/changelogCopy";
|
|
16
|
+
import { dashboardStrings, type Lang } from "@/lib/dashboardCopy";
|
|
17
|
+
import { withDashboardSessionParam } from "@/lib/dashboardSessionNav";
|
|
18
|
+
|
|
19
|
+
type LiveShape = { language?: string };
|
|
20
|
+
|
|
21
|
+
function ChangelogBody() {
|
|
22
|
+
const searchParams = useSearchParams();
|
|
23
|
+
const dashboardSessionNavId = searchParams.get("session");
|
|
24
|
+
const { payload, refresh } = useKronosysPayload();
|
|
25
|
+
const live = payload?.current as LiveShape | undefined;
|
|
26
|
+
const lang: Lang = live?.language === "fr" ? "fr" : "en";
|
|
27
|
+
const dt = dashboardStrings(lang);
|
|
28
|
+
const c = useMemo(() => changelogBundle(lang), [lang]);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="min-h-screen bg-zinc-100 text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100">
|
|
32
|
+
<header className={appShellHeaderClassName}>
|
|
33
|
+
<div className={appShellHeaderToolRowClassName}>
|
|
34
|
+
<div className="flex min-w-0 flex-col gap-1">
|
|
35
|
+
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
|
36
|
+
<Link
|
|
37
|
+
href={withDashboardSessionParam("/", dashboardSessionNavId)}
|
|
38
|
+
className="text-xl font-semibold tracking-tight text-zinc-900 hover:text-violet-700 dark:text-zinc-100 dark:hover:text-violet-300"
|
|
39
|
+
>
|
|
40
|
+
Kronosys
|
|
41
|
+
</Link>
|
|
42
|
+
<span className="text-zinc-400 dark:text-zinc-600" aria-hidden>
|
|
43
|
+
/
|
|
44
|
+
</span>
|
|
45
|
+
<span className="text-lg font-medium text-zinc-700 dark:text-zinc-300">
|
|
46
|
+
{c.title}
|
|
47
|
+
</span>
|
|
48
|
+
</div>
|
|
49
|
+
<p className="flex flex-wrap items-center gap-x-2 text-xs font-medium leading-snug text-zinc-500 dark:text-zinc-400">
|
|
50
|
+
<span>{dt.brandTagline}</span>
|
|
51
|
+
<span className="text-zinc-400/70 dark:text-zinc-600" aria-hidden>
|
|
52
|
+
·
|
|
53
|
+
</span>
|
|
54
|
+
<AppVersionStamp ariaLabelTemplate={dt.appVersionAriaLabel} />
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
58
|
+
<ThemeToggle lang={lang} />
|
|
59
|
+
<PageRefreshButton
|
|
60
|
+
title={dt.pageRefreshTitle}
|
|
61
|
+
ariaLabel={dt.pageRefreshAriaLabel}
|
|
62
|
+
inlineMessages={{
|
|
63
|
+
loading: dt.pageRefreshProgressLabel,
|
|
64
|
+
success: dt.pageRefreshDoneToast,
|
|
65
|
+
error: dt.pageRefreshFailedToast,
|
|
66
|
+
}}
|
|
67
|
+
onRefresh={async () => {
|
|
68
|
+
return await refresh({ routerInvalidate: true });
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
<main className="mx-auto w-full max-w-3xl px-5 pb-16 pt-6 sm:px-8 lg:px-10">
|
|
76
|
+
<p className="text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
|
|
77
|
+
{c.subtitle}
|
|
78
|
+
</p>
|
|
79
|
+
<div className="mt-8 space-y-6">
|
|
80
|
+
{c.entries.length === 0 ? (
|
|
81
|
+
<p className="text-sm text-zinc-500 dark:text-zinc-400">
|
|
82
|
+
{c.empty}
|
|
83
|
+
</p>
|
|
84
|
+
) : (
|
|
85
|
+
c.entries.map((entry) => (
|
|
86
|
+
<section
|
|
87
|
+
key={entry.version}
|
|
88
|
+
className="rounded-xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-700 dark:bg-zinc-800/60"
|
|
89
|
+
>
|
|
90
|
+
<h2 className="text-base font-semibold text-zinc-900 dark:text-zinc-100">
|
|
91
|
+
v{entry.version}
|
|
92
|
+
</h2>
|
|
93
|
+
<ul className="mt-3 list-inside list-disc space-y-1.5 text-sm leading-relaxed text-zinc-700 dark:text-zinc-300">
|
|
94
|
+
{entry.items.map((item) => (
|
|
95
|
+
<li key={item}>{item}</li>
|
|
96
|
+
))}
|
|
97
|
+
</ul>
|
|
98
|
+
</section>
|
|
99
|
+
))
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</main>
|
|
103
|
+
<ScrollToTopFab
|
|
104
|
+
ariaLabel={lang === "fr" ? "Retour en haut de la page" : "Back to top"}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default function ChangelogPage() {
|
|
111
|
+
return (
|
|
112
|
+
<Suspense
|
|
113
|
+
fallback={
|
|
114
|
+
<div className="min-h-screen bg-zinc-100 px-6 py-10 text-sm text-zinc-500 dark:bg-zinc-900">
|
|
115
|
+
Kronosys…
|
|
116
|
+
</div>
|
|
117
|
+
}
|
|
118
|
+
>
|
|
119
|
+
<ChangelogBody />
|
|
120
|
+
</Suspense>
|
|
121
|
+
);
|
|
122
|
+
}
|
package/app/globals.css
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* Paquet `tailwindcss` : dans `apps/dashboard-web/node_modules` (build) et à la racine du dépôt (dev Turbopack). */
|
|
2
|
+
@import "tailwindcss";
|
|
3
|
+
|
|
4
|
+
/* Thème piloté par la classe `dark` sur `<html>` (ThemeProvider), pas par le seul préférence système. */
|
|
5
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
6
|
+
|
|
7
|
+
:root {
|
|
8
|
+
/* Aligné sur le remap zinc clair (chaud, moins d’éblouissement). */
|
|
9
|
+
--background: oklch(93.8% 0.011 82);
|
|
10
|
+
--foreground: oklch(24.5% 0.014 285);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
html.dark {
|
|
14
|
+
/* Zinc 900 — évite le noir absolu (#000 / zinc-950). */
|
|
15
|
+
--background: #18181b;
|
|
16
|
+
--foreground: #f4f4f5;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@theme inline {
|
|
20
|
+
--color-background: var(--background);
|
|
21
|
+
--color-foreground: var(--foreground);
|
|
22
|
+
--font-sans: var(--font-rubik);
|
|
23
|
+
--font-mono: var(--font-geist-mono);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* Thème clair : neutres plus chauds, moins de blanc pur et de zinc « froid ».
|
|
28
|
+
* S’applique uniquement sans `.dark` sur `<html>` ; le mode sombre garde la palette Tailwind par défaut.
|
|
29
|
+
*/
|
|
30
|
+
html:not(.dark) {
|
|
31
|
+
color-scheme: light;
|
|
32
|
+
--color-white: oklch(98.2% 0.004 85);
|
|
33
|
+
--color-zinc-50: oklch(97.2% 0.006 82);
|
|
34
|
+
--color-zinc-100: oklch(93.8% 0.011 82);
|
|
35
|
+
--color-zinc-200: oklch(88.5% 0.012 84);
|
|
36
|
+
--color-zinc-300: oklch(82% 0.014 84);
|
|
37
|
+
--color-zinc-400: oklch(68% 0.016 280);
|
|
38
|
+
--color-zinc-500: oklch(54% 0.018 285);
|
|
39
|
+
--color-zinc-600: oklch(44% 0.017 285);
|
|
40
|
+
--color-zinc-700: oklch(37% 0.014 285);
|
|
41
|
+
--color-zinc-800: oklch(30% 0.012 285);
|
|
42
|
+
--color-zinc-900: oklch(24.5% 0.014 285);
|
|
43
|
+
--color-zinc-950: oklch(18% 0.012 285);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
body {
|
|
47
|
+
background: var(--background);
|
|
48
|
+
color: var(--foreground);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Minuteur focus : clignotement sous 5 min (travail / longue pause), accent violet sous 30 s */
|
|
52
|
+
@keyframes kronosys-krono-focus-blink {
|
|
53
|
+
0%,
|
|
54
|
+
100% {
|
|
55
|
+
opacity: 1;
|
|
56
|
+
}
|
|
57
|
+
50% {
|
|
58
|
+
opacity: 0.2;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.kronosys-krono-focus-time-blink {
|
|
63
|
+
animation: kronosys-krono-focus-blink 0.85s ease-in-out infinite;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Durée de session : au-delà du seuil paramétrable — rouge, clignotement lent */
|
|
67
|
+
@keyframes kronosys-session-duration-alert-blink {
|
|
68
|
+
0%,
|
|
69
|
+
100% {
|
|
70
|
+
opacity: 1;
|
|
71
|
+
}
|
|
72
|
+
50% {
|
|
73
|
+
opacity: 0.45;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.kronosys-session-duration-alert {
|
|
78
|
+
color: rgb(220 38 38);
|
|
79
|
+
animation: kronosys-session-duration-alert-blink 2.6s ease-in-out infinite;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
html.dark .kronosys-session-duration-alert {
|
|
83
|
+
color: rgb(248 113 113);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Saisie tâche « passé » : exactement deux assombrissements (pas de 3e pic dû au easing) */
|
|
87
|
+
@keyframes kronosys-past-datetime-blink-twice-kf {
|
|
88
|
+
0%,
|
|
89
|
+
14% {
|
|
90
|
+
opacity: 1;
|
|
91
|
+
}
|
|
92
|
+
24% {
|
|
93
|
+
opacity: 0.14;
|
|
94
|
+
}
|
|
95
|
+
34% {
|
|
96
|
+
opacity: 1;
|
|
97
|
+
}
|
|
98
|
+
44% {
|
|
99
|
+
opacity: 0.14;
|
|
100
|
+
}
|
|
101
|
+
54%,
|
|
102
|
+
100% {
|
|
103
|
+
opacity: 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.kronosys-past-datetime-blink-twice {
|
|
108
|
+
animation: kronosys-past-datetime-blink-twice-kf 2.35s linear 1 both;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Popover date-heure (react-day-picker) : accent violet, aligné sur le tableau de bord */
|
|
112
|
+
.kronosys-datetime-popover .rdp-root {
|
|
113
|
+
--rdp-accent-color: rgb(147 51 234);
|
|
114
|
+
--rdp-accent-background-color: rgb(147 51 234 / 0.14);
|
|
115
|
+
--rdp-day-height: 2.25rem;
|
|
116
|
+
--rdp-day-width: 2.25rem;
|
|
117
|
+
--rdp-day_button-height: 2.125rem;
|
|
118
|
+
--rdp-day_button-width: 2.125rem;
|
|
119
|
+
--rdp-day_button-border-radius: 0.5rem;
|
|
120
|
+
--rdp-selected-border: 2px solid rgb(147 51 234);
|
|
121
|
+
--rdp-today-color: rgb(109 40 217);
|
|
122
|
+
--rdp-nav-height: 2.25rem;
|
|
123
|
+
--rdp-nav_button-width: 2.25rem;
|
|
124
|
+
--rdp-nav_button-height: 2.25rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.kronosys-datetime-popover .rdp-selected .rdp-day_button {
|
|
128
|
+
background-color: var(--rdp-accent-color);
|
|
129
|
+
border-color: var(--rdp-accent-color);
|
|
130
|
+
color: #fafafa;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
html.dark .kronosys-datetime-popover .rdp-root {
|
|
134
|
+
--rdp-accent-color: rgb(167 139 250);
|
|
135
|
+
--rdp-accent-background-color: rgb(139 92 246 / 0.22);
|
|
136
|
+
--rdp-selected-border: 2px solid rgb(167 139 250);
|
|
137
|
+
--rdp-today-color: rgb(196 181 253);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
html.dark .kronosys-datetime-popover .rdp-selected .rdp-day_button {
|
|
141
|
+
color: rgb(24 24 27);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Listes natives heure / minutes : forcer le schéma sombre + fond lisible (sinon menu clair + texte illisible). */
|
|
145
|
+
html.dark .kronosys-datetime-popover select {
|
|
146
|
+
color-scheme: dark;
|
|
147
|
+
background-color: rgb(39 39 42);
|
|
148
|
+
color: rgb(244 244 245);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
html.dark .kronosys-datetime-popover select option {
|
|
152
|
+
background-color: rgb(24 24 27);
|
|
153
|
+
color: rgb(250 250 250);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.kronosys-datetime-popover select {
|
|
157
|
+
color-scheme: light;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Krono Focus : au démarrage (idle → running), deux clignotements + scale +5 % */
|
|
161
|
+
@keyframes kronosys-krono-focus-start-pulse {
|
|
162
|
+
0% {
|
|
163
|
+
opacity: 1;
|
|
164
|
+
transform: scale(1);
|
|
165
|
+
}
|
|
166
|
+
20% {
|
|
167
|
+
opacity: 0.28;
|
|
168
|
+
transform: scale(1.05);
|
|
169
|
+
}
|
|
170
|
+
40% {
|
|
171
|
+
opacity: 1;
|
|
172
|
+
transform: scale(1);
|
|
173
|
+
}
|
|
174
|
+
60% {
|
|
175
|
+
opacity: 0.28;
|
|
176
|
+
transform: scale(1.05);
|
|
177
|
+
}
|
|
178
|
+
80%,
|
|
179
|
+
100% {
|
|
180
|
+
opacity: 1;
|
|
181
|
+
transform: scale(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.kronosys-krono-focus-start-pulse {
|
|
186
|
+
animation: kronosys-krono-focus-start-pulse 0.85s ease-in-out 1 both;
|
|
187
|
+
transform-origin: center center;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@media (prefers-reduced-motion: reduce) {
|
|
191
|
+
html {
|
|
192
|
+
scroll-behavior: auto !important;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.kronosys-krono-focus-time-blink {
|
|
196
|
+
animation: none;
|
|
197
|
+
opacity: 1;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.kronosys-krono-focus-start-pulse {
|
|
201
|
+
animation: none;
|
|
202
|
+
opacity: 1;
|
|
203
|
+
transform: none;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.kronosys-past-datetime-blink-twice {
|
|
207
|
+
animation: none;
|
|
208
|
+
opacity: 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
|
|
3
|
+
export const metadata: Metadata = {
|
|
4
|
+
title: "Guide d’utilisation — Kronosys",
|
|
5
|
+
description:
|
|
6
|
+
"Aide intégrée : navigation, thème, sessions, tâches, KronoFocus, rapports et paramètres (Kronosys).",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function GuideLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
10
|
+
return children;
|
|
11
|
+
}
|