fix: improve release visibility and cache handling

This commit is contained in:
2026-07-29 09:28:29 +02:00
parent 71c78476a2
commit d30d7e7ecc
7 changed files with 149 additions and 28 deletions
+16 -1
View File
@@ -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
+32 -1
View File
@@ -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
+42 -6
View File
@@ -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 [
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
# ============================================================================
+13 -2
View File
@@ -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.
+1 -1
View File
@@ -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"
+4 -5
View File
@@ -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."
]
}
+37 -8
View File
@@ -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', () => {