refactor: address code-review findings for install detection and diag log

- pwa-install.ts: permanent 'ponomar-ever-installed' marker keeps install
  button hidden on Firefox/Safari after appinstalled clears the offer flag;
  isPwaInstalled moved to core for reuse
- diag-log: self-heal corrupted storage, show MM-DD in tail
- settings: refresh diag panel after clear-cache; uniform completion style
- data-cache.has: plain count(key); document HTML-as-failure intent
This commit is contained in:
catarrh committed 2026-08-23 10:23:36 +03:00
1 parent 193038a671
commit 142b95d226
6 files changed
+90 -33

No files matched your search

+1 -1
View File
@@ -102,7 +102,7 @@ export class DataCache {
const db = await getDB();
return await new Promise<boolean | null>((resolve) => {
const tx = db.transaction(STORE_NAME, 'readonly');
const request = tx.objectStore(STORE_NAME).count(IDBKeyRange.only(key));
const request = tx.objectStore(STORE_NAME).count(key);
request.onsuccess = () => resolve(request.result > 0);
request.onerror = () => resolve(null);
});
+17 -6
View File
@@ -15,10 +15,21 @@ export interface DiagEvent {
[key: string]: unknown;
}
function readEvents(): DiagEvent[] {
const raw = localStorage.getItem(DIAG_KEY);
if (!raw) return [];
try {
const arr = JSON.parse(raw);
if (Array.isArray(arr)) return arr as DiagEvent[];
} catch { /* fall through */ }
// Corrupted or non-array value — reset so logging can recover
localStorage.removeItem(DIAG_KEY);
return [];
}
export function diagLog(event: string, detail?: Record<string, unknown>) {
try {
const raw = localStorage.getItem(DIAG_KEY);
const arr: DiagEvent[] = raw ? JSON.parse(raw) : [];
const arr = readEvents();
arr.push({ t: new Date().toISOString(), event, ...(detail ?? {}) });
while (arr.length > MAX_EVENTS) arr.shift();
localStorage.setItem(DIAG_KEY, JSON.stringify(arr));
@@ -27,16 +38,16 @@ export function diagLog(event: string, detail?: Record<string, unknown>) {
export function getDiagLog(): DiagEvent[] {
try {
const raw = localStorage.getItem(DIAG_KEY);
const arr = raw ? JSON.parse(raw) : [];
return Array.isArray(arr) ? arr : [];
return readEvents();
} catch {
return [];
}
}
function formatEvent(e: DiagEvent): string {
const time = (e.t || '').slice(11, 19);
// MM-DD HH:MM:SS (local relevance comes from ISO UTC; day boundary matters
// when events span days)
const time = `${(e.t || '').slice(5, 10)} ${(e.t || '').slice(11, 19)}`;
const parts: string[] = [];
for (const [k, v] of Object.entries(e)) {
if (k === 't' || k === 'event') continue;
+2
View File
@@ -188,6 +188,8 @@ export class OfflineManager {
}
if (!resp.ok) continue;
const ct = resp.headers.get('content-type');
// HTML body (SPA fallback / captive portal) is not our data —
// counts as a failure and feeds the circuit-breaker streak
if (ct && ct.includes('text/html')) break;
let data: unknown;
+46
View File
@@ -0,0 +1,46 @@
/**
* PWA installation state detection.
*
* 'ponomar-ever-installed' is a permanent marker (never removed): set when
* the app is installed, launched in standalone mode, or inherited from the
* legacy 'ponomar-installed' offer flag. Used to hide the Settings install
* button on browsers without getInstalledRelatedApps() (Firefox, Safari).
* Distinct from 'ponomar-installed', which only gates the first-launch
* offline-offer modal and is cleared on appinstalled for re-offer semantics.
*/
const EVER_INSTALLED_KEY = 'ponomar-ever-installed';
export function isStandaloneDisplay(): boolean {
return (
window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone === true
);
}
export function markEverInstalled(): void {
try {
localStorage.setItem(EVER_INSTALLED_KEY, '1');
} catch { /* best-effort */ }
}
export function hasEverInstalledMarker(): boolean {
try {
return localStorage.getItem(EVER_INSTALLED_KEY) === '1';
} catch {
return false;
}
}
export async function isPwaInstalled(): Promise<boolean> {
if (isStandaloneDisplay()) return true;
if (hasEverInstalledMarker()) return true;
try {
const navAny = navigator as any;
if (typeof navAny.getInstalledRelatedApps === 'function') {
const apps = await navAny.getInstalledRelatedApps();
if (Array.isArray(apps) && apps.length > 0) return true;
}
} catch { /* not supported */ }
return false;
}
+11 -3
View File
@@ -12,6 +12,7 @@ import { loadSettings, type Settings } from './settings-view';
import { getTranslations, type LanguageCode } from '../core/i18n';
import { DataCache } from '../core/data-cache';
import { diagLog } from '../core/diag-log';
import { markEverInstalled, hasEverInstalledMarker } from '../core/pwa-install';
type View = 'calendar' | 'bible' | 'prayer' | 'akathists' | 'parimii' | 'horologion' | 'sbornik' | 'paraclete' | 'irmologion' | 'menaion' | 'triodion' | 'lives' | 'settings';
@@ -139,13 +140,20 @@ export class App {
// Clear first-launch flag on re-install
window.addEventListener('appinstalled', () => {
localStorage.removeItem('ponomar-installed');
markEverInstalled();
});
// Show offline offer on first standalone launch
const isStandalone = () => window.matchMedia('(display-mode: standalone)').matches || (window.navigator as any).standalone;
if (isStandalone() && !localStorage.getItem('ponomar-installed')) {
localStorage.setItem('ponomar-installed', '1');
this.showOfflineOfferModal();
if (isStandalone()) {
markEverInstalled();
if (!localStorage.getItem('ponomar-installed')) {
localStorage.setItem('ponomar-installed', '1');
this.showOfflineOfferModal();
}
} else if (localStorage.getItem('ponomar-installed') && !hasEverInstalledMarker()) {
// One-time migration: legacy offer flag implies the app was installed
markEverInstalled();
}
// Auto-preload essential data for the current language
+13 -23
View File
@@ -6,6 +6,7 @@ import { getTranslations, type LanguageCode } from '../core/i18n';
import { OfflineManager } from '../core/offline-manager';
import { DataCache } from '../core/data-cache';
import { diagTail } from '../core/diag-log';
import { isPwaInstalled } from '../core/pwa-install';
const LANGUAGES = [
{ code: 'en', name: 'English', local: 'English' },
@@ -417,7 +418,7 @@ export class SettingsView {
if (!hintEl) return;
const ua = navigator.userAgent;
let hint: string;
if (await this.isPwaInstalled()) {
if (await isPwaInstalled()) {
hint = t.settings.installPwaAlreadyInstalled;
} else if (/firefox/i.test(ua)) {
hint = t.settings.installPwaHintFirefox;
@@ -435,7 +436,7 @@ export class SettingsView {
const btn = document.getElementById('install-pwa');
if (!btn || !('serviceWorker' in navigator)) return;
try {
if (!(await this.isPwaInstalled())) btn.classList.remove('hidden');
if (!(await isPwaInstalled())) btn.classList.remove('hidden');
} catch { /* stay hidden */ }
})();
@@ -602,6 +603,8 @@ export class SettingsView {
if (statsEl) statsEl.textContent = t.settings.offlineCleared;
const progressDiv = document.getElementById('offline-progress');
if (progressDiv) progressDiv.classList.add('hidden');
const diagEl = document.getElementById('diag-log');
if (diagEl) diagEl.textContent = diagTail();
});
// Show initial storage stats
@@ -694,12 +697,14 @@ export class SettingsView {
}).then((prog) => {
if (progressBar) progressBar.style.width = '100%';
if (progressText) {
const failList = prog.failedFiles?.length ? `: ${prog.failedFiles.join(', ')}` : '';
progressText.textContent = prog.aborted
? t.settings.offlineAborted
: prog.failed > 0
? `${t.settings.offlineDone} ${prog.failed} ${t.settings.offlineFailed}${failList}`
: t.settings.offlineDone;
if (prog.aborted) {
progressText.textContent = t.settings.offlineAborted;
} else if (prog.failed > 0) {
const failList = prog.failedFiles?.length ? `: ${prog.failedFiles.join(', ')}` : '';
progressText.textContent = `${t.settings.offlineDone} ${prog.failed} ${t.settings.offlineFailed}${failList}`;
} else {
progressText.textContent = t.settings.offlineDone;
}
}
const diagEl = document.getElementById('diag-log');
if (diagEl) diagEl.textContent = diagTail();
@@ -737,19 +742,4 @@ export class SettingsView {
preview.style.fontSize = `${this.settings.fontSize}px`;
}
}
private async isPwaInstalled(): Promise<boolean> {
if (window.matchMedia('(display-mode: standalone)').matches || (window.navigator as any).standalone === true) {
return true;
}
if (localStorage.getItem('ponomar-installed')) return true;
try {
const navAny = navigator as any;
if (typeof navAny.getInstalledRelatedApps === 'function') {
const apps = await navAny.getInstalledRelatedApps();
if (Array.isArray(apps) && apps.length > 0) return true;
}
} catch { /* not supported */ }
return false;
}
}