diff --git a/PROJECT_KNOWLEDGE.md b/PROJECT_KNOWLEDGE.md index 75e8d44..d71912d 100644 --- a/PROJECT_KNOWLEDGE.md +++ b/PROJECT_KNOWLEDGE.md @@ -55,6 +55,21 @@ If `build_info.json` is missing, malformed, or does not contain a usable value, The data is loaded centrally in `app.py` and made available to every template as `build_info`. +## Cache and Release Visibility + +RollCalc uses the current build identifier to cache-bust RollCalc-owned static assets referenced by active templates. The preferred identifier is `build_info.commit`; if it is missing or `unknown`, the fallback is `build_info.version`. + +Dynamic HTML responses are sent with `Cache-Control: no-cache` so the calculator and Help pages are revalidated after deployments. Static assets are not globally marked no-cache; versioned URLs are used for active static JSON assets instead. + +For releases, keep these sources aligned: + +- `build_info.json` +- the default `VERSION` in `scripts/update_build_info.sh`, unless deployment overrides it +- the newest heading in `docs/de/recent_changes.md` +- `version` in `static/recent_changes.json` + +The Recent Changes page includes a persistent force-reload fallback note for users whose browser still shows an older interface after deployment. + ## Help and User Manual The in-app Help area keeps `docs/de/user_manual.md` as the leading German documentation source. RollCalc reads the Markdown file on each `/help/user-manual` request and renders it server-side with `markdown-it-py`. @@ -105,7 +120,7 @@ The short dialog item list is maintained separately in: static/recent_changes.json ``` -This JSON is a compact summary for the dialog, not the complete release history. Keep it aligned with the most relevant current entries from `docs/de/recent_changes.md`. +This JSON is a compact summary for the dialog, not the complete release history. It contains a `version` field and an `items` list. The browser displays the dialog only when the JSON version matches `build_info.version`; mismatched or invalid JSON must not block the calculator. ## Authentication diff --git a/README.md b/README.md index e39b186..d6accc7 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,38 @@ The short dialog summary is maintained in: static/recent_changes.json ``` -Keep `docs/de/recent_changes.md` as the complete release-note source and `static/recent_changes.json` as the short 3-5 item summary for the startup dialog. +The JSON is versioned: + +```json +{ + "version": "v0.4-beta2", + "items": [] +} +``` + +The dialog loads this file in the browser and only displays the items when `static/recent_changes.json.version` matches `build_info.version`. Keep `docs/de/recent_changes.md` as the complete user-facing release-note source and `static/recent_changes.json` as the short 3-5 item summary for the currently deployed version. + +### Browser Cache and Release Visibility + +RollCalc uses the build identifier as a cache-busting query parameter for RollCalc-owned static assets referenced by active templates. The preferred identifier is `build_info.commit`; if it is missing or `unknown`, the app falls back to `build_info.version`. + +Dynamic HTML responses use: + +```text +Cache-Control: no-cache +``` + +This requires browser revalidation for the calculator and Help pages while allowing versioned static assets to be cached normally. + +For each deployment, keep these sources aligned: + +- `build_info.json` +- `scripts/update_build_info.sh` default `VERSION`, unless overridden during deployment +- newest section in `docs/de/recent_changes.md` +- `version` in `static/recent_changes.json` + +If RollCalc does not display the latest interface after an update, perform a force reload: +`Ctrl+F5` or `Ctrl+Shift+R` on Windows/Linux, `Cmd+Shift+R` on macOS. ## User Manual PDF Export diff --git a/app.py b/app.py index 184ccfd..0f66c1e 100644 --- a/app.py +++ b/app.py @@ -75,23 +75,32 @@ BUILD_INFO = load_build_info() def load_whats_new_summary(): - """Load compact release notes for the What's New dialog.""" + """Load compact release-note metadata for the What's New dialog.""" try: with open(WHATS_NEW_SUMMARY_FILE, "r", encoding="utf-8") as f: data = json.load(f) except Exception as e: print(f"[What's New Error] {e}") - return [] + return {"version": "unknown", "items": []} - items = data.get("items") if isinstance(data, dict) else None + if not isinstance(data, dict): + return {"version": "unknown", "items": []} + + version = data.get("version") + items = data.get("items") if not isinstance(items, list): - return [] + items = [] - return [ - item.strip() - for item in items - if isinstance(item, str) and item.strip() - ][:5] + return { + "version": version.strip() + if isinstance(version, str) and version.strip() + else "unknown", + "items": [ + item.strip() + for item in items + if isinstance(item, str) and item.strip() + ][:5] + } WHATS_NEW_SUMMARY = load_whats_new_summary() @@ -101,9 +110,36 @@ def inject_build_info(): """Make build metadata and release-note summary available in templates.""" return { "build_info": BUILD_INFO, - "whats_new_summary": WHATS_NEW_SUMMARY + "whats_new_summary": WHATS_NEW_SUMMARY, + "static_asset_url": static_asset_url } + +def get_static_asset_version(build_info=None): + """Return the preferred cache-busting build identifier.""" + info = build_info or BUILD_INFO + for key in ("commit", "version"): + value = info.get(key) if isinstance(info, dict) else None + if isinstance(value, str) and value.strip() and value.strip() != "unknown": + return value.strip() + return None + + +def static_asset_url(filename): + """Build a URL for RollCalc-owned static assets with a build query.""" + asset_version = get_static_asset_version() + if asset_version: + return url_for("serve_static", filename=filename, v=asset_version) + return url_for("serve_static", filename=filename) + + +@app.after_request +def add_html_cache_policy(response): + """Require revalidation for dynamic HTML while leaving static caching intact.""" + if response.mimetype == "text/html": + response.headers["Cache-Control"] = "no-cache" + return response + # ============================================================================ # AUTHENTICATION # ============================================================================ diff --git a/docs/de/recent_changes.md b/docs/de/recent_changes.md index 374e432..97742dc 100644 --- a/docs/de/recent_changes.md +++ b/docs/de/recent_changes.md @@ -1,10 +1,21 @@ # Recent Changes -## Version 0.4 +## v0.4-beta2 — Current Version + +- English Quick Reference added to the integrated Help area for the main calculation and transport-analysis workflows. +- Release notes and What’s New now focus on the currently deployed version. +- Browser cache handling improved so updated frontend data is loaded more reliably after deployments. + +## After an Update + +If RollCalc does not display the latest interface after an update, perform a force reload: +Ctrl+F5 or Ctrl+Shift+R on Windows/Linux, +Cmd+Shift+R on macOS. + +## Earlier Changes - User Manual integrated into RollCalc. - Help area added for internal documentation. -- English Quick Reference added to the integrated Help area for the main calculation and transport-analysis workflows. - Capacity Analysis presentation improved. - Governing Constraint highlighted more clearly. - Remaining Capacity added to the Capacity Summary. diff --git a/scripts/update_build_info.sh b/scripts/update_build_info.sh index 85acf6b..3a470a5 100755 --- a/scripts/update_build_info.sh +++ b/scripts/update_build_info.sh @@ -3,7 +3,7 @@ set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -VERSION="${VERSION:-v0.4-dev}" +VERSION="${VERSION:-v0.4-beta2}" cd "$PROJECT_ROOT" diff --git a/static/recent_changes.json b/static/recent_changes.json index b0e47a5..807625d 100644 --- a/static/recent_changes.json +++ b/static/recent_changes.json @@ -1,9 +1,8 @@ { + "version": "v0.4-beta2", "items": [ - "User Manual integrated", - "English Quick Reference added to the Help area", - "Capacity Analysis improved", - "Governing Constraint clarified", - "Remaining Capacity added" + "English Quick Reference added to the Help area.", + "Recent Changes now identifies the current deployed version.", + "Browser cache handling improved for updated frontend data." ] } diff --git a/templates/roll_calculator.html b/templates/roll_calculator.html index d31b31c..8e911e9 100644 --- a/templates/roll_calculator.html +++ b/templates/roll_calculator.html @@ -1023,6 +1023,8 @@ // ===================================================== window.ARTICLE_DATA = []; window.APP_CONFIG = {"forklift_rules":{"applicable_categories":["bentofix"],"rules":[{"weight_threshold_kg":1700,"max_weight_kg":2750,"requirements":{"min_core_outer_diameter_mm":170},"messages":{"en":{"warning_title":"Heavy Roll – Special Equipment Required","core_ok":"Core diameter OK (≥ 170 mm)","core_nok":"Core diameter too small"}}}],"hard_limit":{"max_weight_kg":2750,"messages":{"en":{"block_title":"Weight Limit Exceeded","block_text":"Roll weight exceeds 2750 kg maximum"}}}}}; +const ROLLCALC_ARTICLE_DATA_URL = {{ static_asset_url('article-data.json')|tojson }}; +const ROLLCALC_RECENT_CHANGES_JSON_URL = {{ static_asset_url('recent_changes.json')|tojson }}; // ===================================================== // LOAD ARTICLE DATA @@ -1087,7 +1089,7 @@ function populateArticleDropdowns() { (async function() { try { - const resp = await fetch('/static/article-data.json'); + const resp = await fetch(ROLLCALC_ARTICLE_DATA_URL); if (resp.ok) { window.ARTICLE_DATA = await resp.json(); console.log('✅ Article data loaded:', window.ARTICLE_DATA.length, 'items'); @@ -1645,7 +1647,6 @@ document.addEventListener('DOMContentLoaded', () => { }); const ROLLCALC_BUILD_VERSION = {{ build_info.version|tojson }}; -const ROLLCALC_WHATS_NEW_ITEMS = {{ whats_new_summary|tojson }}; const ROLLCALC_LAST_SEEN_VERSION_KEY = 'rollcalc_last_seen_version'; function shouldShowWhatsNew(currentVersion, storedVersion) { @@ -1675,16 +1676,41 @@ function renderWhatsNewItems(items) { }); } -function maybeShowWhatsNew() { +async function loadWhatsNewData(currentVersion) { + try { + const resp = await fetch(ROLLCALC_RECENT_CHANGES_JSON_URL); + if (!resp.ok) { + console.warn('Could not load RollCalc release notes summary:', resp.status); + return null; + } + + const data = await resp.json(); + if (!data || typeof data.version !== 'string' || !Array.isArray(data.items)) { + console.warn('Invalid RollCalc release notes summary format'); + return null; + } + + if (data.version !== currentVersion) { + return null; + } + + const items = data.items.filter(item => + typeof item === 'string' && item.trim() + ); + return items.length ? { version: data.version, items } : null; + } catch (e) { + console.warn('Could not load RollCalc release notes summary', e); + return null; + } +} + +async function maybeShowWhatsNew() { const overlay = document.getElementById('whatsNewOverlay'); const versionValue = document.getElementById('whatsNewVersion'); const continueBtn = document.getElementById('whatsNewContinueBtn'); const openChangesBtn = document.getElementById('whatsNewOpenChangesBtn'); - const items = Array.isArray(ROLLCALC_WHATS_NEW_ITEMS) - ? ROLLCALC_WHATS_NEW_ITEMS - : []; - if (!overlay || !versionValue || !continueBtn || !openChangesBtn || items.length === 0) return; + if (!overlay || !versionValue || !continueBtn || !openChangesBtn) return; let storedVersion = null; try { @@ -1695,8 +1721,11 @@ function maybeShowWhatsNew() { if (!shouldShowWhatsNew(ROLLCALC_BUILD_VERSION, storedVersion)) return; + const releaseData = await loadWhatsNewData(ROLLCALC_BUILD_VERSION); + if (!releaseData) return; + versionValue.textContent = ROLLCALC_BUILD_VERSION; - renderWhatsNewItems(items); + renderWhatsNewItems(releaseData.items); overlay.classList.remove('is-hidden'); continueBtn.addEventListener('click', () => {