- .ai-specs/PROJECT_CONTEXT.md: new 'Language Model (UI vs. Content)' section
(serviceLang ru->cu, fallback chain, scripture, Bible tab independence,
fonts, calendar authority) + content-policy pointer in Critical Invariants.
- .ai-specs/WORKING_POLICIES.md: new 'Liturgical Content Policies' —
never translate service/Bible texts (sourced only), cu texts verbatim
(no normalisation/latinisation), service assembly follows the Typicon.
- DATA_EN.md/DATA_RUS.md: ru/ holds enriched lives only; services fall back
to cu; cu is the authoritative calendar + service texts.
- validate-data now scans cu/services/prayers (349 files; 331 -> 680 total).
- marker types (TROPARION/KONTAKION/THEOTOKION/LITANY/COMMUNION) factored
into PRAYER_NODE_TYPES with a keep-in-sync comment.
- COMMUNION marker (cu COMMUNION/SUN.json) added per the same convention.
Both node types are bare classification markers with no payload
(content lives in TEXT/HEADER nodes, matching the TROPARION/KONTAKION
convention already in the allowlist). Clears the 13 pre-existing
validate errors; no data or rendering change.
bibleVersion() is only ever indexed with serviceLang (en|cu), so the
fr/el/la/zh/ar entries were unreachable; the shared fallback URL
(/data/shared/bible/...) does not exist on disk. No behavior change.
- loadManifest() goes through DataCache.fetchWithFallback: the file list is
written to IDB and survives SW-cache eviction; a run with the reduced
fallback set is flagged and shown as degraded (new i18n en/ru/cu).
- 404 answers are counted as 'missing' (deployment drift) with a
preload-missing diag entry, instead of silently reporting Done.
- preload() is re-entrancy safe: concurrent calls join the in-flight run.
- versions.json is loaded once per session (was fetched per translation).
- Write-through failures in fetchWithFallback()/fetch() log idb-write-fail.
- preload-manifest.json SW route is NetworkFirst (fresh coverage online).
- Manifest now also covers the shared/lives + shared/commands fallbacks.
Root cause of 'pre-cache done but offline data missing':
1. Preload manifest never listed the services/{prayers,texts,headers,var}
subtrees the Service view dereferences (CommonPrayers->prayers, etc.),
plus only 13 hardcoded commands. Generator now enumerates all of them
(en 1500->1740, ru->1333, cu 1500->2108 files).
2. DataCache.set silently resolved on IDB write failure (quota exceeded),
so preload counted un-stored files as success and reported 100% done.
set() now returns boolean; preload treats failed writes as failures
that feed the existing circuit breaker (visible abort + diag entry).
3. No persistence request: added best-effort navigator.storage.persist()
before preload, diag-logged.
4. Dropped dead manifest weight (menaion-common/add, horologionadd) -
never referenced by any view.
PrayerView's select displayed the first option natively while activeSection
stayed null, so content only appeared after manually changing selection.
Initialize to first section and load it on render for all 8 collections.
- registerType 'prompt' + skipWaiting/clientsClaim false: new SW waits
instead of silently hijacking running sessions
- main.ts registers via virtual:pwa-register; onNeedRefresh shows
dismissible toast (i18n en/ru/cu) with Update button -> updateSW(true)
- No polling — update check only on navigation, zero extra traffic
- Settings About shows __BUILD_DATE__ build stamp for remote debugging
- DataCache.fetchWithFallback: network-first with IDB write-through under
the same url: keys the preload writes; falls back to precached IDB copy
when offline — closes the preload(IDB) vs read(SW cache) gap that made
the Bible reader (and every other view) fail offline despite precaching
- Convert all 33 raw /data fetches across bible/service/menaion/prayer/
calendar/settings views; in-memory caches and notFound maps preserved
- fetchCommandText: switch to lowercase services/commands/ (already in
preload manifest); tolerate both TEXT/text node casings per migrate-data
- Remove stale duplicate static/data/en/services/Command/ (15 files)
- fetchLives: fix misc fallback to real lives/misc/{N}.json bundles
- OfflineManager.preload accepts bibleTranslations: 'all' and resolves
translation ids internally from versions.json (single source), removing
the fragile settings-side fetch that could throw and abort the whole
download on mobile first launch
- Decouple trigger from '#settings/autopreload' route param via one-shot
requestAutoPreload()/consumeAutoPreload() flag
- Auto-expand + tick Bible variant checkboxes for UI consistency
- Offer modal sets the flag and navigates to plain '#settings'
- Button hidden by default; async isPwaInstalled() check (standalone mode,
ponomar-installed flag, getInstalledRelatedApps) unhides only if installable
- Click hint 'already installed' branch uses same multi-signal detection
- manifest id '/' for getInstalledRelatedApps matching on desktop Chrome
- Circuit breaker: abort preload after 5 consecutive network failures
(success or 404 resets streak) instead of grinding doomed retries
- localStorage diag ring buffer (survives cache wipes): preload-start/
done/abort, manifest-unavailable, version events; shown in Settings
- checkDataVersion: no longer wipes IDB on transient read errors
(DataCache.has tri-state probe); logs old value on real migrations
- loadManifest: null result no longer cached forever (per-session fallback lock-in)
- Install button without beforeinstallprompt now shows inline per-platform
instructions (Firefox/iOS/generic) instead of silent no-op
- Preload: 404s no longer counted as failures nor retried
- PreloadProgress.failedFiles (up to 10) surfaced in completion message
- loadMenaionBundle: ru resolves to cu bundle (no RU liturgical texts by design)
- autoPreload: fetch cu bundle once; keep ru+cu lives for current month
- calendar view: fetch shared commemorations only for mapped feast CIds
- Default calendar type julian -> gregorian (new installs only)
- EN nav 'Sbornik' -> 'Hymnal', section title -> 'Liturgical Hymnal'
- Day header: text-sm on date lines, weekday dropped from Julian line,
order: Julian / day-title / Gregorian