v1.3.1: Smart Cache Strategy + REQUIREMENTS.md überarbeitet

- Feature: isCacheFresh() – initialer API-Refresh nur wenn Cache >1h alt oder <5 Arbeitstage abgedeckt (FR-024)
- REQUIREMENTS.md komplett überarbeitet: lösungsneutral, alle Features dokumentiert, Versions-Spalte
- Build-Script: Git-Tag wird bei Rebuild auf aktuellen Commit verschoben (git tag -f)
- Neue Regel 7: Requirements-Konsistenz im Implementation Plan
- test_logic.js: statischer Check für isCacheFresh
This commit is contained in:
2026-02-17 20:38:53 +01:00
parent 4dbd6c930f
commit fe86e68aca
11 changed files with 156 additions and 58 deletions

View File

@@ -48,3 +48,10 @@ trigger: always_on
## 6. Workspace Scopes
- **Browser**: Allowed for documentation and safe browsing. No automated logins without permission.
- **Terminal**: No `rm -rf`. Run tests (`pytest` etc.) after logic changes.
## 7. Requirements-Konsistenz 📋
Alle umgesetzten Anforderungen müssen mit `REQUIREMENTS.md` übereinstimmen.
1. **Vor der Umsetzung prüfen**: Passt die neue Anforderung zu den bestehenden Requirements?
2. **Fehlende Requirements**: Wenn eine umzusetzende Anforderung in `REQUIREMENTS.md` fehlt, muss dies im **Implementation Plan** explizit dokumentiert und freigegeben werden.
3. **Widersprüche**: Wenn eine neue Anforderung im Widerspruch zu bestehenden Requirements steht, muss dies im **Implementation Plan** als ⚠️ Warning hervorgehoben werden.
4. **Nach Freigabe**: Bei Genehmigung des Implementation Plans muss `REQUIREMENTS.md` entsprechend erweitert oder korrigiert werden (neue ID, Version, Beschreibung). Obsolet gewordene Requirements werden nicht aus `REQUIREMENTS.md` gelöscht sondern nur als durchgestrichen markiert und ein Kommentar mit dem Grund hinzugefügt.

View File

@@ -9,51 +9,65 @@ Das System umfasst die Darstellung von Menüplänen in einer Wochenübersicht, d
## 2. Funktionale Anforderungen
| ID | Anforderung (Satzschablone nach Chris Rupp) | Priorität | Status |
| ID | Anforderung (Satzschablone nach Chris Rupp) | Priorität | Seit |
|:---|:---|:---|:---|
| **Authentifizierung** | | | |
| FR-001 | Das System muss dem Benutzer die Möglichkeit bieten, sich mit Mitarbeiternummer und Passwort anzumelden. | Hoch | |
| FR-002 | Das System muss eine bestehende Bessa-Session erkennen und automatisch wiederverwenden, um eine erneute Anmeldung zu vermeiden. | Hoch | |
| FR-003 | Das System darf keine Zugangsdaten dauerhaft speichern. Die Authentifizierung muss sitzungsbasiert sein. | Hoch | |
| FR-004 | Dem Benutzer muss angezeigt werden, ob und als wer er angemeldet ist (Name oder ID). | Mittel | |
| **Authentifizierung & Zugang** | | | |
| FR-001 | Das System muss dem Benutzer die Möglichkeit bieten, sich mit Mitarbeiternummer und Passwort anzumelden. | Hoch | v1.0.1 |
| FR-002 | Das System muss eine bestehende Bessa-Session erkennen und automatisch wiederverwenden, um eine erneute Anmeldung zu vermeiden. | Hoch | v1.0.1 |
| FR-003 | Das System darf keine Zugangsdaten dauerhaft speichern. Die Authentifizierung muss sitzungsbasiert sein. | Hoch | v1.0.1 |
| FR-004 | Dem Benutzer muss angezeigt werden, ob und als wer er angemeldet ist (Vorname, Name oder ID). | Mittel | v1.0.1 |
| FR-005 | Nicht authentifizierte Benutzer müssen die Menüdaten einsehen können (eingeschränkter Lesezugriff). | Mittel | v1.0.1 |
| FR-006 | Das System muss eine explizite Logout-Funktion bereitstellen, die alle sitzungsbezogenen Daten entfernt. | Mittel | v1.0.1 |
| **Menüanzeige** | | | |
| FR-010 | Das System muss dem Benutzer alle verfügbaren Tagesmenüs einer Woche gleichzeitig in einer Übersicht darstellen. | Hoch | |
| FR-011 | Das System muss dem Benutzer die Navigation zwischen der aktuellen und der kommenden Woche ermöglichen. | Mittel | |
| FR-012 | Für jedes Gericht müssen Name, Beschreibung, Preis und Verfügbarkeitsstatus angezeigt werden. | Hoch | |
| FR-013 | Bereits bestellte Menüs müssen visuell von nicht bestellten unterscheidbar sein (farbliche Markierung, Badge). | Mittel | |
| FR-014 | Die Tageskarten-Header müssen den Bestellstatus des Tages farblich signalisieren (bestellt / bestellbar / nicht bestellbar). | Niedrig | |
| FR-015 | Wenn neue Menüdaten geladen werden, muss der Fortschritt dem Benutzer in Echtzeit angezeigt werden. | Niedrig | |
| FR-016 | Das System muss bereits geladene Menüdaten zwischenspeichern (Caching), um bei erneutem Aufruf sofort eine Übersicht anzeigen zu können. | Mittel | ✅ |
| FR-010 | Das System muss dem Benutzer alle verfügbaren Tagesmenüs einer Woche gleichzeitig in einer Übersicht darstellen. | Hoch | v1.0.1 |
| FR-011 | Das System muss dem Benutzer die Navigation zwischen der aktuellen und der kommenden Woche ermöglichen. | Mittel | v1.0.1 |
| FR-012 | Für jedes Gericht müssen Name, Beschreibung, Preis und Verfügbarkeitsstatus angezeigt werden. | Hoch | v1.0.1 |
| FR-013 | Bereits bestellte Menüs müssen visuell von nicht bestellten unterscheidbar sein (farbliche Markierung, Badge). | Mittel | v1.0.1 |
| FR-014 | Die Tageskarten-Header müssen den Bestellstatus des Tages farblich signalisieren (bestellt / bestellbar / nicht bestellbar). | Niedrig | v1.0.1 |
| FR-015 | Bestellte Menü-Codes (z.B. M1, M2) müssen als Badges im Tageskarten-Header angezeigt werden. | Niedrig | v1.0.1 |
| FR-016 | Am heutigen Tag müssen bestellte Menüs an erster Stelle sortiert werden. | Niedrig | v1.0.1 |
| FR-017 | Wenn keine Menüdaten für eine Woche vorliegen, muss ein aussagekräftiger Leertext angezeigt werden. | Niedrig | v1.0.1 |
| **Daten-Aktualisierung** | | | |
| FR-020 | Wenn Menüdaten geladen werden, muss der Fortschritt dem Benutzer in Echtzeit angezeigt werden (Fortschrittsbalken, Statustext). | Niedrig | v1.0.1 |
| FR-021 | Das System muss bereits geladene Menüdaten zwischenspeichern, um bei erneutem Aufruf sofort eine Übersicht anzeigen zu können. | Mittel | v1.0.1 |
| FR-022 | Das System muss dem Benutzer die Möglichkeit bieten, die Menüdaten manuell neu zu laden. | Niedrig | v1.0.1 |
| FR-023 | Der Zeitpunkt der letzten Aktualisierung muss für den Benutzer sichtbar sein. | Niedrig | v1.0.1 |
| FR-024 | Das System darf beim Start keinen automatischen API-Refresh durchführen, wenn der Cache frisch (< 1 Stunde) und vollständig ist (nächste 5 Arbeitstage abgedeckt). | Mittel | v1.3.1 |
| **Bestellfunktion** | | | |
| FR-020 | Authentifizierte Benutzer müssen ein verfügbares Menü direkt aus der Übersicht bestellen können. | Hoch | |
| FR-021 | Authentifizierte Benutzer müssen eine bestehende Bestellung direkt aus der Übersicht stornieren können. | Hoch | |
| FR-022 | Nach Bestellschluss (Cutoff-Zeit) dürfen keine neuen Bestellungen oder Stornierungen für diesen Tag möglich sein. | Hoch | |
| FR-023 | Es muss möglich sein, dasselbe Menü mehrfach zu bestellen. | Niedrig | |
| FR-030 | Authentifizierte Benutzer müssen ein verfügbares Menü direkt aus der Übersicht bestellen können. | Hoch | v1.0.1 |
| FR-031 | Authentifizierte Benutzer müssen eine bestehende Bestellung direkt aus der Übersicht stornieren können. | Hoch | v1.0.1 |
| FR-032 | Nach Bestellschluss (Cutoff-Zeit) dürfen keine neuen Bestellungen oder Stornierungen für diesen Tag möglich sein. | Hoch | v1.0.1 |
| FR-033 | Es muss möglich sein, dasselbe Menü mehrfach zu bestellen. Bei Mehrfachbestellungen muss die Anzahl angezeigt werden. | Niedrig | v1.0.1 |
| **Kostentransparenz** | | | |
| FR-030 | Das System muss die Gesamtkosten aller Bestellungen einer Woche automatisch berechnen und anzeigen. | Mittel | |
| FR-040 | Das System muss die Gesamtkosten aller Bestellungen einer Woche automatisch berechnen und anzeigen. | Mittel | v1.1.0 |
| **Bestell-Countdown** | | | |
| FR-040 | Das System muss eine Stunde vor Bestellschluss einen visuellen Countdown anzeigen. | Mittel | |
| FR-050 | Das System muss vor Bestellschluss einen visuell hervorgehobenen Countdown anzeigen. | Mittel | v1.1.0 |
| **Menü-Flagging & Benachrichtigungen** | | | |
| FR-050 | Authentifizierte Benutzer müssen ausverkaufte Menüs zur Beobachtung markieren können ("flaggen"). | Mittel | |
| FR-051 | Das System muss geflaggte Menüs periodisch auf Verfügbarkeitsänderungen prüfen. | Mittel | |
| FR-052 | Bei Statusänderung eines geflaggten Menüs auf „verfügbar" muss der Benutzer benachrichtigt werden (In-App Toast + Systembenachrichtigung). | Mittel | |
| FR-053 | Geflaggte Menüs müssen im UI visuell hervorgehoben werden (gelber Glow bei ausverkauft, grüner Glow bei verfügbar). | Mittel | |
| FR-054 | Wenn die Bestell-Cutoff-Zeit erreicht ist, muss das System ein Flag automatisch entfernen. | Mittel | |
| FR-060 | Authentifizierte Benutzer müssen ausverkaufte Menüs zur Beobachtung markieren können ("flaggen"). | Mittel | v1.0.1 |
| FR-061 | Das System muss geflaggte Menüs periodisch auf Verfügbarkeitsänderungen prüfen. | Mittel | v1.0.1 |
| FR-062 | Bei Statusänderung eines geflaggten Menüs auf „verfügbar" muss der Benutzer benachrichtigt werden (In-App + Systembenachrichtigung). | Mittel | v1.0.1 |
| FR-063 | Geflaggte Menüs müssen im UI visuell hervorgehoben werden (gelber Glow bei ausverkauft, grüner Glow bei verfügbar). | Mittel | v1.0.1 |
| FR-064 | Wenn die Bestell-Cutoff-Zeit erreicht ist, muss das System ein Flag automatisch entfernen. | Mittel | v1.0.1 |
| **Personalisierung: Smart Highlights** | | | |
| FR-060 | Benutzer müssen Schlagwörter (Tags) definieren können, nach denen Menüs automatisch visuell hervorgehoben werden. | Mittel | |
| FR-061 | Die Hervorhebung muss anhand von Menüname und Beschreibung erfolgen (Substring-Matching, case-insensitive). | Niedrig | |
| FR-062 | Hervorgehobene Menüs müssen ein Tag-Badge mit dem matchenden Schlagwort anzeigen. | Niedrig | |
| FR-063 | Die nächste-Woche-Navigation muss die Anzahl der dort gefundenen Highlights als Badge anzeigen. | Niedrig | |
| FR-070 | Benutzer müssen Schlagwörter (Tags) definieren können, nach denen Menüs automatisch visuell hervorgehoben werden. | Mittel | v1.1.0 |
| FR-071 | Die Hervorhebung muss anhand von Menüname und Beschreibung erfolgen (Substring-Matching, case-insensitive). | Niedrig | v1.1.0 |
| FR-072 | Hervorgehobene Menüs müssen ein Tag-Badge mit dem matchenden Schlagwort anzeigen. | Niedrig | v1.2.4 |
| FR-073 | Die Nächste-Woche-Navigation muss die Anzahl der dort gefundenen Highlights als Badge anzeigen. | Niedrig | v1.2.5 |
| **Darstellung & Theming** | | | |
| FR-070 | Das System muss einen hellen und einen dunklen Darstellungsmodus (Light/Dark Theme) unterstützen. | Niedrig | |
| FR-071 | Die Theme-Präferenz des Benutzers muss persistent gespeichert werden. | Niedrig | |
| FR-072 | Das System muss beim erstmaligen Laden die System-Präferenz (`prefers-color-scheme`) berücksichtigen. | Niedrig | |
| FR-080 | Das System muss einen hellen und einen dunklen Darstellungsmodus (Light/Dark Theme) unterstützen. | Niedrig | v1.0.1 |
| FR-081 | Die Theme-Präferenz des Benutzers muss persistent gespeichert werden. | Niedrig | v1.0.1 |
| FR-082 | Das System muss beim erstmaligen Laden die Betriebssystem-Präferenz für das Farbschema berücksichtigen. | Niedrig | v1.0.1 |
| **Benutzer-Feedback** | | | |
| FR-090 | Alle benutzerrelevanten Aktionen (Bestellung, Stornierung, Fehler) müssen durch nicht-blockierende Benachrichtigungen (Toasts) bestätigt werden. | Mittel | v1.0.1 |
| FR-091 | Bei einem Verbindungsfehler muss ein Fehlerdialog mit Fallback-Link zur Originalseite angezeigt werden. | Mittel | v1.0.1 |
| **Nächste-Woche-Badge** | | | |
| FR-100 | Die Navigation zur nächsten Woche muss ein Badge anzeigen, das den Überblick über den Bestellstatus der kommenden Woche visualisiert (bestellt / bestellbar / gesamt). | Niedrig | v1.0.1 |
| **Update-Management** | | | |
| FR-080 | Das System muss periodisch prüfen, ob eine neuere Version verfügbar ist. | Niedrig | |
| FR-081 | Bei Verfügbarkeit einer neueren Version muss ein diskreter Indikator im Header angezeigt werden. | Niedrig | |
| FR-082 | Benutzer müssen eine Versionsliste mit Installationslinks einsehen können (Versionsmenü). | Niedrig | |
| FR-083 | Es muss möglich sein, zu einer älteren Version zurückzukehren (Downgrade). | Niedrig | |
| FR-084 | Ein Dev-Mode muss es ermöglichen, zwischen stabilen Releases und Entwicklungs-Tags umzuschalten. | Niedrig | |
| FR-110 | Das System muss periodisch prüfen, ob eine neuere Version verfügbar ist. | Niedrig | v1.0.3 |
| FR-111 | Bei Verfügbarkeit einer neueren Version muss ein diskreter Indikator im Header angezeigt werden. | Niedrig | v1.0.3 |
| FR-112 | Benutzer müssen eine Versionsliste mit Installationslinks einsehen können (Versionsmenü). | Niedrig | v1.3.0 |
| FR-113 | Es muss möglich sein, zu einer älteren Version zurückzukehren (Downgrade). | Niedrig | v1.3.0 |
| FR-114 | Ein Dev-Mode muss es ermöglichen, zwischen stabilen Releases und Entwicklungs-Tags umzuschalten. | Niedrig | v1.3.0 |
## 3. Nicht-funktionale Anforderungen
@@ -66,7 +80,7 @@ Das System umfasst die Darstellung von Menüplänen in einer Wochenübersicht, d
| **Benutzbarkeit** | NFR-005 | Die Oberfläche muss auf mobilen Geräten fehlerfrei nutzbar sein. | Viewports ab 320px Breite |
| **Benutzbarkeit** | NFR-006 | Alle interaktiven Elemente müssen Tooltips oder Hilfetexte bieten. | 100% Coverage |
| **Benutzbarkeit** | NFR-007 | Die Benutzeroberfläche muss vollständig in deutscher Sprache sein. | Vollständige Lokalisierung |
| **Wartbarkeit** | NFR-008 | Die Build-Artefakte müssen durch automatisierte Tests validiert werden. | Build-Tests vorhanden |
| **Wartbarkeit** | NFR-008 | Die Build-Artefakte müssen durch automatisierte Tests validiert werden. | Build-Tests + Logik-Tests |
## 4. Technische Randbedingungen
* **Deployment**: Das System wird als Bookmarklet ausgeliefert, das auf der Bessa-Webseite ausgeführt wird.

View File

@@ -271,7 +271,8 @@ echo "✅ All build tests passed."
echo ""
echo "=== Tagging $VERSION ==="
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo " Tag $VERSION already exists, skipping."
git tag -f "$VERSION"
echo "🔄 Tag $VERSION moved to current commit."
else
git tag "$VERSION"
echo "✅ Created tag: $VERSION"

View File

@@ -1,3 +1,6 @@
## v1.3.1 (2026-02-17)
- **Feature**: Smart Cache Initialer API-Refresh wird übersprungen wenn Cache < 1h alt und nächste 5 Arbeitstage abgedeckt. ⚡
## v1.3.0 (2026-02-16)
- **Feature**: GitHub Release Management 📦
- Version-Menü: Klick auf Versionsnummer zeigt alle verfügbaren Versionen

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16
dist/install.html vendored

File diff suppressed because one or more lines are too long

View File

@@ -1807,7 +1807,7 @@ body {
<div class="brand">
<span class="material-icons-round logo-icon">restaurant_menu</span>
<div class="header-left">
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.3.0</small></h1>
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.3.1</small></h1>
<div id="last-updated-subtitle" class="subtitle"></div>
</div>
</div>
@@ -1919,7 +1919,7 @@ body {
</div>
<div class="modal-body">
<div style="margin-bottom: 1rem;">
<strong>Aktuell:</strong> <span id="version-current">v1.3.0</span>
<strong>Aktuell:</strong> <span id="version-current">v1.3.1</span>
</div>
<div class="dev-toggle">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
@@ -2532,6 +2532,34 @@ body {
return false;
}
// FR-024: Check if cache is fresh enough to skip API refresh
function isCacheFresh() {
const cachedTs = localStorage.getItem(CACHE_TS_KEY);
if (!cachedTs) return false;
// Condition 1: Cache < 1 hour old
const ageMs = Date.now() - new Date(cachedTs).getTime();
if (ageMs > 60 * 60 * 1000) return false;
// Condition 2: Data covers next 5 working days
const today = new Date();
today.setHours(0, 0, 0, 0);
const cachedDates = new Set();
allWeeks.forEach(w => (w.days || []).forEach(d => cachedDates.add(d.date)));
let coveredDays = 0;
for (let i = 0; i < 7 && coveredDays < 5; i++) {
const check = new Date(today);
check.setDate(check.getDate() + i);
const dow = check.getDay();
if (dow === 0 || dow === 6) continue; // Skip weekends
const dateStr = check.toISOString().split('T')[0];
if (cachedDates.has(dateStr)) coveredDays++;
}
return coveredDays >= 5;
}
// === Menu Data Fetching (Direct from Bessa API) ===
async function loadMenuDataFromAPI() {
const loading = document.getElementById('loading');
@@ -3243,7 +3271,7 @@ body {
// Periodic update check (runs on init + every hour)
async function checkForUpdates() {
const currentVersion = 'v1.3.0';
const currentVersion = 'v1.3.1';
const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
try {
@@ -3284,7 +3312,7 @@ body {
const modal = document.getElementById('version-modal');
const container = document.getElementById('version-list-container');
const devToggle = document.getElementById('dev-mode-toggle');
const currentVersion = 'v1.3.0';
const currentVersion = 'v1.3.1';
if (!modal) return;
modal.classList.remove('hidden');
@@ -3487,13 +3515,19 @@ body {
updateAuthUI();
cleanupExpiredFlags();
// Load cached data first for instant UI, then refresh from API
// Load cached data first for instant UI, refresh only if stale (FR-024)
const hadCache = loadMenuCache();
if (hadCache) {
// Hide loading spinner since cache is shown
document.getElementById('loading').classList.add('hidden');
if (!isCacheFresh()) {
console.log('Cache stale or incomplete refreshing from API');
loadMenuDataFromAPI();
} else {
console.log('Cache fresh & complete skipping API refresh');
}
} else {
loadMenuDataFromAPI();
}
loadMenuDataFromAPI();
// Auto-start polling if already logged in
if (authToken) {

View File

@@ -796,6 +796,34 @@
return false;
}
// FR-024: Check if cache is fresh enough to skip API refresh
function isCacheFresh() {
const cachedTs = localStorage.getItem(CACHE_TS_KEY);
if (!cachedTs) return false;
// Condition 1: Cache < 1 hour old
const ageMs = Date.now() - new Date(cachedTs).getTime();
if (ageMs > 60 * 60 * 1000) return false;
// Condition 2: Data covers next 5 working days
const today = new Date();
today.setHours(0, 0, 0, 0);
const cachedDates = new Set();
allWeeks.forEach(w => (w.days || []).forEach(d => cachedDates.add(d.date)));
let coveredDays = 0;
for (let i = 0; i < 7 && coveredDays < 5; i++) {
const check = new Date(today);
check.setDate(check.getDate() + i);
const dow = check.getDay();
if (dow === 0 || dow === 6) continue; // Skip weekends
const dateStr = check.toISOString().split('T')[0];
if (cachedDates.has(dateStr)) coveredDays++;
}
return coveredDays >= 5;
}
// === Menu Data Fetching (Direct from Bessa API) ===
async function loadMenuDataFromAPI() {
const loading = document.getElementById('loading');
@@ -1751,13 +1779,19 @@
updateAuthUI();
cleanupExpiredFlags();
// Load cached data first for instant UI, then refresh from API
// Load cached data first for instant UI, refresh only if stale (FR-024)
const hadCache = loadMenuCache();
if (hadCache) {
// Hide loading spinner since cache is shown
document.getElementById('loading').classList.add('hidden');
if (!isCacheFresh()) {
console.log('Cache stale or incomplete refreshing from API');
loadMenuDataFromAPI();
} else {
console.log('Cache fresh & complete skipping API refresh');
}
} else {
loadMenuDataFromAPI();
}
loadMenuDataFromAPI();
// Auto-start polling if already logged in
if (authToken) {

View File

@@ -107,7 +107,8 @@ try {
[/function\s+fetchVersions/, 'fetchVersions function'],
[/function\s+isNewer/, 'isNewer function'],
[/function\s+openVersionMenu/, 'openVersionMenu function'],
[/kantine_dev_mode/, 'dev-mode localStorage key']
[/kantine_dev_mode/, 'dev-mode localStorage key'],
[/function\s+isCacheFresh/, 'isCacheFresh function']
];
for (const [regex, label] of checks) {

View File

@@ -1 +1 @@
v1.3.0
v1.3.1