Réutilise l'API Releases de Forgejo (publique, sans authentification) comme source de vérité pour "quelle est la dernière version" — zéro nouvel endpoint backend : useAppUpdateCheck compare App.getInfo().version (@capacitor/app, nouvelle dépendance) au tag_name de GET .../releases/latest, et pointe "Mettre à jour" vers l'URL stable de téléchargement (issue #65). Comparaison de version en logique pure (version-compare.ts), séparée du hook effectful — testée isolément via un test de composant Cypress (version-compare.cy.tsx, même patron que highlight-tech-steps.cy.tsx : aucun montage, juste describe/it/expect sur des fonctions pures). Natif uniquement (Capacitor.isNativePlatform()) : @capacitor/app et @capacitor/preferences restent en import dynamique dans le hook, jamais dans le bundle web (même patron que capacitor-token-store.ts) — vérifié dans le build (deux petits chunks séparés, pas dans le bundle principal). Vérification au démarrage à froid + à la reprise depuis l'arrière-plan (appStateChange), avec un throttle de 6h et une mémorisation de la version déjà rejetée dans @capacitor/preferences pour ne jamais re-harceler sur la même version. Bannière discrète (pas une Dialog modale), bouton "Mettre à jour" ouvrant le lien de téléchargement dans le navigateur système plutôt qu'un flux PackageInstaller in-app. Vérifié : pnpm lint, pnpm --filter web build, le test de composant (9/9), et layout/smoke/planning-page e2e (18/18, la bannière étant un no-op sur le web ne devait rien casser — confirmé). Closes #66 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
50 lines
1.2 KiB
SCSS
50 lines
1.2 KiB
SCSS
// Styles for UpdateBanner — a discreet, dismissible strip above the routed
|
|
// page content (see AppLayout.tsx). Uses --color-warning the same way
|
|
// source-item-preview__hint does elsewhere in the app: "worth noticing",
|
|
// not an error.
|
|
.update-banner {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--space-sm);
|
|
margin-bottom: var(--space-md);
|
|
padding: var(--space-sm) var(--space-md);
|
|
background: color-mix(in srgb, var(--color-warning) 12%, var(--color-surface));
|
|
border: 1px solid var(--color-warning);
|
|
border-radius: var(--radius-base);
|
|
font-size: var(--font-size-sm);
|
|
|
|
&__text {
|
|
color: var(--color-text);
|
|
}
|
|
|
|
&__actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&__update {
|
|
padding: var(--space-xs) var(--space-sm);
|
|
background: var(--color-primary);
|
|
color: var(--color-surface);
|
|
border-radius: var(--radius-base);
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
&__dismiss {
|
|
padding: var(--space-xs) var(--space-sm);
|
|
background: none;
|
|
border: none;
|
|
color: var(--color-text-muted);
|
|
cursor: pointer;
|
|
|
|
&:hover {
|
|
color: var(--color-text);
|
|
}
|
|
}
|
|
}
|