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

@@ -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) {