@forgeroute/ui 0.4.77 → 0.4.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/forgeroute-ui-charts.mjs +30 -22
- package/fesm2022/forgeroute-ui-charts.mjs.map +1 -1
- package/fesm2022/forgeroute-ui-maps.mjs +26 -18
- package/fesm2022/forgeroute-ui-maps.mjs.map +1 -1
- package/fesm2022/forgeroute-ui-messaging.mjs +629 -544
- package/fesm2022/forgeroute-ui-messaging.mjs.map +1 -1
- package/fesm2022/forgeroute-ui-offline.mjs +3036 -0
- package/fesm2022/forgeroute-ui-offline.mjs.map +1 -0
- package/fesm2022/forgeroute-ui-pdf.mjs +104 -71
- package/fesm2022/forgeroute-ui-pdf.mjs.map +1 -1
- package/fesm2022/forgeroute-ui-stripe.mjs +291 -156
- package/fesm2022/forgeroute-ui-stripe.mjs.map +1 -1
- package/fesm2022/forgeroute-ui.mjs +14021 -5678
- package/fesm2022/forgeroute-ui.mjs.map +1 -1
- package/package.json +5 -1
- package/types/forgeroute-ui-charts.d.ts +10 -4
- package/types/forgeroute-ui-maps.d.ts +5 -2
- package/types/forgeroute-ui-messaging.d.ts +57 -36
- package/types/forgeroute-ui-offline.d.ts +887 -0
- package/types/forgeroute-ui-pdf.d.ts +24 -12
- package/types/forgeroute-ui-stripe.d.ts +72 -54
- package/types/forgeroute-ui.d.ts +3299 -338
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forgeroute-ui-offline.mjs","names":[],"sources":["../../../projects/forgeroute-ui/offline/src/config/offline-config.ts","../../../projects/forgeroute-ui/offline/src/config/uuid.ts","../../../projects/forgeroute-ui/offline/src/state/connection-state.service.ts","../../../projects/forgeroute-ui/offline/src/state/stale-data.service.ts","../../../projects/forgeroute-ui/offline/src/store/offline-store.ts","../../../projects/forgeroute-ui/offline/src/http/errors.ts","../../../projects/forgeroute-ui/offline/src/store/temp-ids.ts","../../../projects/forgeroute-ui/offline/src/replay/offline-replayer.ts","../../../projects/forgeroute-ui/offline/src/http/request-timeout.ts","../../../projects/forgeroute-ui/offline/src/http/idempotency.interceptor.ts","../../../projects/forgeroute-ui/offline/src/http/connection.interceptor.ts","../../../projects/forgeroute-ui/offline/src/support/offline-support.service.ts","../../../projects/forgeroute-ui/offline/src/http/offline.interceptor.ts","../../../projects/forgeroute-ui/offline/src/http/offline-http-interceptors.ts","../../../projects/forgeroute-ui/offline/src/support/provide-offline-support.ts","../../../projects/forgeroute-ui/offline/src/ui/connection-banner/connection-banner.component.ts","../../../projects/forgeroute-ui/offline/src/ui/relative-time.ts","../../../projects/forgeroute-ui/offline/src/ui/pending-changes-drawer/pending-changes-drawer.component.ts","../../../projects/forgeroute-ui/offline/src/ui/stale-data-chip/stale-data-chip.component.ts","../../../projects/forgeroute-ui/offline/src/ui/still-working/still-working.component.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport type { OfflineMutation } from '../store/offline-store';\n\n/** string = path prefix or substring after the origin is stripped; RegExp is tested on path + query. */\nexport type UrlMatcher = string | RegExp;\n\nexport interface QueueRule {\n pattern: RegExp;\n methods: readonly string[];\n /** Plain-words name of the change shown in the pending-changes list (e.g. \"Add lot 12\"). */\n label?: (method: string, url: string, body: unknown) => string;\n entityType?: (url: string) => string | undefined;\n}\n\nexport interface OfflineSupportConfig {\n /** Only URLs containing this are intercepted. Default '/api/'. */\n apiPrefix?: string;\n /** Never touched (auth, public, uploads...). */\n skip?: readonly UrlMatcher[];\n /** The signed-in user's id; null = nobody (nothing is queued/cached). Scopes the store. */\n getUserId: () => string | null;\n /** ALLOW-LIST mode: only requests matching a rule may be queued offline. Omit for BLOCK-LIST mode (everything may be queued except `blockOffline`). */\n allowQueue?: readonly QueueRule[];\n /** Writes that must NEVER be queued (payments, sends...). While offline/unreachable they fail with OfflineBlockedError. */\n blockOffline?: readonly { pattern: UrlMatcher; message?: string }[];\n /** GET endpoints that must never be stored or served from the device copy. */\n neverCache?: readonly UrlMatcher[];\n /** Safe extra headers copied onto the queued row and re-sent on replay (never Authorization). */\n replayHeaders?: readonly string[];\n /** Cheap authenticated/unauthenticated GET used ONLY while the connection is suspect. null/omitted = no probe. */\n probe?: { url: string; intervalMs?: number /* default 15000 */ } | null;\n /** Client timeouts in ms. Defaults: read 30000, write 60000, long 180000. `long` applies to blob/arraybuffer, FormData, and matchers in `longRequests`. */\n timeouts?: { read?: number; write?: number; long?: number };\n longRequests?: readonly UrlMatcher[];\n /** Slow detection thresholds. Defaults: slowMs 5000, slowCount 2 within windowMs 60000; single in-flight > stuckMs 8000; recover after quietMs 30000 of fast requests. */\n slow?: { slowMs?: number; slowCount?: number; windowMs?: number; stuckMs?: number; quietMs?: number };\n /** IndexedDB database name. Default 'forgeroute-offline'. */\n dbName?: string;\n /** Cache TTLs in ms (default 10 min lists / 30 min details); `ttl(url)` overrides. */\n cacheTtl?: (url: string) => number;\n maxQueue?: number; // default 500\n maxAttempts?: number; // default 5\n /**\n * Reads the id the server gave a record that was created offline, from the 2xx answer of the replayed POST (`body` is the parsed JSON answer,\n * `undefined` when the answer was empty / not JSON). Used to rewrite later saved changes that point at the client-made temporary id.\n * Default: `body.id`, else `body.data.id`; a string or a finite number. Return `undefined` when the answer has no id (dependent changes then fail\n * with kind `dependency` instead of being sent with a wrong id).\n */\n createdId?: (body: unknown, row: OfflineMutation) => string | number | undefined;\n}\n\nexport const OFFLINE_SUPPORT_CONFIG = new InjectionToken<OfflineSupportConfig>('OFFLINE_SUPPORT_CONFIG');\n\n/** Header the replayer sets on every replayed request; the idempotency and offline interceptors let such requests through untouched. */\nexport const OFFLINE_REPLAY_HEADER = 'X-Offline-Replay';\nexport const IDEMPOTENCY_HEADER = 'Idempotency-Key';\n/** Response header on an answer served from the device copy; the value is the `cachedAt` time in ms. */\nexport const FROM_CACHE_HEADER = 'X-From-Offline-Cache';\n\nexport interface ResolvedSlowConfig { slowMs: number; slowCount: number; windowMs: number; stuckMs: number; quietMs: number }\n\n/** The config with every default filled in. */\nexport interface ResolvedOfflineConfig {\n apiPrefix: string;\n skip: readonly UrlMatcher[];\n getUserId: () => string | null;\n allowQueue: readonly QueueRule[] | null;\n blockOffline: readonly { pattern: UrlMatcher; message?: string }[];\n neverCache: readonly UrlMatcher[];\n replayHeaders: readonly string[];\n probe: { url: string; intervalMs: number } | null;\n timeouts: { read: number; write: number; long: number };\n longRequests: readonly UrlMatcher[];\n slow: ResolvedSlowConfig;\n dbName: string;\n cacheTtl: ((url: string) => number) | null;\n maxQueue: number;\n maxAttempts: number;\n createdId: (body: unknown, row: OfflineMutation) => string | number | undefined;\n}\n\nfunction usableId(v: unknown): string | number | undefined {\n if (typeof v === 'string') return v.trim() === '' ? undefined : v;\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n return undefined;\n}\n\n/** Default for `createdId`: `body.id`, else `body.data.id` (string or finite number), otherwise undefined. */\nexport function defaultCreatedId(body: unknown): string | number | undefined {\n if (body === null || typeof body !== 'object') return undefined;\n const b = body as { id?: unknown; data?: unknown };\n const direct = usableId(b.id);\n if (direct !== undefined) return direct;\n const data = b.data;\n return data !== null && typeof data === 'object' ? usableId((data as { id?: unknown }).id) : undefined;\n}\n\n/** Wraps an app-supplied `createdId` so that it can never throw and never return something that is not a usable id. */\nfunction safeCreatedId(fn: NonNullable<OfflineSupportConfig['createdId']>): ResolvedOfflineConfig['createdId'] {\n return (body, row) => {\n try { return usableId(fn(body, row)); } catch { return undefined; }\n };\n}\n\nexport const DEFAULT_LIST_TTL_MS = 10 * 60 * 1000;\nexport const DEFAULT_DETAIL_TTL_MS = 30 * 60 * 1000;\n\nfunction positive(value: number | undefined, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;\n}\n\nexport function resolveConfig(cfg: OfflineSupportConfig): ResolvedOfflineConfig {\n return {\n apiPrefix: cfg.apiPrefix || '/api/',\n skip: cfg.skip ?? [],\n getUserId: cfg.getUserId,\n allowQueue: cfg.allowQueue ?? null,\n blockOffline: cfg.blockOffline ?? [],\n neverCache: cfg.neverCache ?? [],\n replayHeaders: cfg.replayHeaders ?? [],\n probe: cfg.probe && cfg.probe.url ? { url: cfg.probe.url, intervalMs: positive(cfg.probe.intervalMs, 15000) } : null,\n timeouts: {\n read: positive(cfg.timeouts?.read, 30000),\n write: positive(cfg.timeouts?.write, 60000),\n long: positive(cfg.timeouts?.long, 180000),\n },\n longRequests: cfg.longRequests ?? [],\n slow: {\n slowMs: positive(cfg.slow?.slowMs, 5000),\n slowCount: positive(cfg.slow?.slowCount, 2),\n windowMs: positive(cfg.slow?.windowMs, 60000),\n stuckMs: positive(cfg.slow?.stuckMs, 8000),\n quietMs: positive(cfg.slow?.quietMs, 30000),\n },\n dbName: cfg.dbName || 'forgeroute-offline',\n cacheTtl: cfg.cacheTtl ?? null,\n maxQueue: positive(cfg.maxQueue, 500),\n maxAttempts: positive(cfg.maxAttempts, 5),\n createdId: cfg.createdId ? safeCreatedId(cfg.createdId) : defaultCreatedId,\n };\n}\n\n/** Path + query of a URL with the origin removed ('https://x.test/api/a?b=1' -> '/api/a?b=1'). Relative URLs are returned as they are. */\nexport function pathAndQuery(url: string): string {\n const m = /^[a-z][a-z0-9+.-]*:\\/\\/[^/?#]+(.*)$/i.exec(url);\n const rest = m ? m[1] : url;\n const noHash = rest.split('#')[0];\n return noHash === '' ? '/' : noHash;\n}\n\n/** Path only (no query) of a URL with the origin removed. */\nexport function pathOnly(url: string): string {\n return pathAndQuery(url).split('?')[0];\n}\n\n/** True when `matcher` matches `url`: a string anywhere in the path + query, a RegExp tested on path + query (a global/sticky RegExp is not made stateful). */\nexport function matchesUrl(url: string, matcher: UrlMatcher): boolean {\n const target = pathAndQuery(url);\n if (typeof matcher === 'string') return matcher !== '' && target.includes(matcher);\n matcher.lastIndex = 0;\n const hit = matcher.test(target);\n matcher.lastIndex = 0;\n return hit;\n}\n\nexport function matchesAny(url: string, matchers: readonly UrlMatcher[]): boolean {\n return matchers.some(m => matchesUrl(url, m));\n}\n\nexport function isMutationMethod(method: string): boolean {\n const m = method.toUpperCase();\n return m === 'POST' || m === 'PUT' || m === 'PATCH' || m === 'DELETE';\n}\n\nconst ID_SEGMENT = /^(?:\\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;\n\n/** First id-looking path segment (digits or UUID) with the segment before it as the entity type: '/api/lots/42/notes' -> { entityType: 'lots', entityId: '42' }. */\nexport function deriveEntity(url: string): { entityType?: string; entityId?: string } {\n const segments = pathOnly(url).split('/').filter(Boolean);\n for (let i = 1; i < segments.length; i++) {\n if (ID_SEGMENT.test(segments[i])) return { entityType: segments[i - 1], entityId: segments[i] };\n }\n return {};\n}\n\n/** The path prefix whose stored GET copies a successful write may have made stale: the path without trailing id segments ('/api/contacts/12' -> '/api/contacts'). */\nexport function invalidationPrefix(url: string): string {\n const segments = pathOnly(url).split('/').filter(Boolean);\n while (segments.length > 1 && ID_SEGMENT.test(segments[segments.length - 1])) segments.pop();\n return '/' + segments.join('/');\n}\n\nconst resolvedCache = new WeakMap<OfflineSupportConfig, ResolvedOfflineConfig>();\n\n/** `resolveConfig` memoised per config object (interceptors run on every request). */\nexport function resolveConfigCached(cfg: OfflineSupportConfig): ResolvedOfflineConfig {\n let resolved = resolvedCache.get(cfg);\n if (!resolved) {\n resolved = resolveConfig(cfg);\n resolvedCache.set(cfg, resolved);\n }\n return resolved;\n}\n\n/** True when the request is one the offline layer looks at: under `apiPrefix` and not matched by `skip`. */\nexport function isInScope(url: string, cfg: ResolvedOfflineConfig): boolean {\n return url.includes(cfg.apiPrefix) && !matchesAny(url, cfg.skip);\n}\n\nexport function lastSegmentIsId(url: string): boolean {\n const segments = pathOnly(url).split('/').filter(Boolean);\n return segments.length > 0 && ID_SEGMENT.test(segments[segments.length - 1]);\n}\n","/**\n * A v4 UUID. Uses `crypto.randomUUID` when the browser has it (Safari < 15.4 and insecure contexts do not),\n * then `crypto.getRandomValues`, then `Math.random` as the last resort. Always 36 characters.\n */\nexport function generateUuid(): string {\n const c: Crypto | undefined = typeof globalThis !== 'undefined' ? (globalThis as { crypto?: Crypto }).crypto : undefined;\n if (c && typeof c.randomUUID === 'function') {\n try { return c.randomUUID(); } catch { /* insecure context: fall through */ }\n }\n const bytes = new Uint8Array(16);\n if (c && typeof c.getRandomValues === 'function') {\n c.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0'));\n return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex.slice(6, 8).join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10, 16).join('')}`;\n}\n","import { DOCUMENT } from '@angular/common';\nimport { DestroyRef, Injectable, computed, inject, signal } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { OFFLINE_SUPPORT_CONFIG, resolveConfig } from '../config/offline-config';\n\nexport type ConnectionState = 'online' | 'slow' | 'unreachable' | 'offline';\n\nexport interface RecordRequestInfo {\n durationMs: number;\n ok: boolean;\n status: number;\n timedOut?: boolean;\n method: string;\n /** Long requests (blob / FormData / `longRequests`) never count as slow. Additive to the documented shape. */\n long?: boolean;\n}\n\n/** What made the connection usable again: the browser `online` event / a normal request that got an answer, or the probe. */\nexport type RecoveryTrigger = 'online' | 'probe';\n\n/** Debounce for the browser `online` event (flapping wifi). `offline` is applied at once: queueing is always the safe side. */\nexport const ONLINE_DEBOUNCE_MS = 300;\nconst PROBE_TIMEOUT_MS = 5000;\n\n/**\n * The one answer to \"can this device reach the server?\": online | slow | unreachable | offline.\n * Measured from real request timings (works in Safari/Firefox, which have no `navigator.connection`), the browser online/offline events and an\n * optional probe GET that runs only while the connection is suspect.\n */\n@Injectable({ providedIn: 'root' })\nexport class ConnectionStateService {\n private readonly cfg = resolveConfig(inject(OFFLINE_SUPPORT_CONFIG, { optional: true }) ?? { getUserId: () => null });\n private readonly win = inject(DOCUMENT).defaultView;\n\n private readonly browserOnline = signal(this.readNavigatorOnline());\n private readonly unreachable = signal(false);\n private readonly slowFlag = signal(false);\n private readonly _slowRequestActive = signal(false);\n private readonly _inFlightCount = signal(0);\n private readonly _state = signal<ConnectionState>(this.browserOnline() ? 'online' : 'offline');\n private readonly _lastChangeAt = signal(Date.now());\n\n readonly state = this._state.asReadonly();\n readonly isOnline = computed(() => this._state() === 'online' || this._state() === 'slow');\n readonly isOffline = computed(() => this._state() === 'offline' || this._state() === 'unreachable');\n /** Some request has been in flight longer than `slow.stuckMs`. */\n readonly slowRequestActive = this._slowRequestActive.asReadonly();\n readonly inFlightCount = this._inFlightCount.asReadonly();\n readonly lastChangeAt = this._lastChangeAt.asReadonly();\n /** True while the user should be told \"this is taking a while\" (a request is stuck or the connection is slow). */\n readonly slowRequestHint = computed(() => this._slowRequestActive() || this._state() === 'slow');\n /** Emits when the connection became usable again; the replayer listens to it. */\n readonly recovered$ = new Subject<RecoveryTrigger>();\n\n private failures: number[] = [];\n private slowTimes: number[] = [];\n private quietTimer: ReturnType<typeof setTimeout> | null = null;\n private onlineTimer: ReturnType<typeof setTimeout> | null = null;\n private probeTimer: ReturnType<typeof setInterval> | null = null;\n private optimisticTimer: ReturnType<typeof setTimeout> | null = null;\n private probeInFlight: Promise<boolean> | null = null;\n private readonly inFlight = new Map<number, { start: number; stuck: boolean; timer: ReturnType<typeof setTimeout> | null }>();\n private nextRequestId = 0;\n\n private readonly onWindowOnline = () => this.scheduleOnline();\n private readonly onWindowOffline = () => { this.cancelOnlineTimer(); this.setBrowserOnline(false); };\n private readonly onFocus = () => this.syncFromNavigator();\n\n constructor() {\n const win = this.win;\n if (win) {\n win.addEventListener('online', this.onWindowOnline);\n win.addEventListener('offline', this.onWindowOffline);\n win.addEventListener('focus', this.onFocus);\n win.document?.addEventListener('visibilitychange', this.onFocus);\n }\n inject(DestroyRef).onDestroy(() => this.teardown());\n }\n\n /** Called by `connectionInterceptor` when a request finished (or failed / timed out). */\n recordRequest(info: RecordRequestInfo): void {\n const now = Date.now();\n if (info.timedOut || info.status === 0) {\n this.handleFailure(now);\n return;\n }\n // The server answered (any HTTP status): it is reachable.\n this.failures = [];\n if (this.unreachable()) this.clearUnreachable('online');\n if (info.long) return;\n if (info.durationMs >= this.cfg.slow.slowMs) {\n this.slowTimes = this.slowTimes.filter(t => now - t <= this.cfg.slow.windowMs);\n this.slowTimes.push(now);\n if (this.slowTimes.length >= this.cfg.slow.slowCount || this.slowFlag()) this.markSlow();\n }\n }\n\n /**\n * Starts tracking one request; call the returned function when it ends (success, error, timeout or cancel; calling it twice is harmless).\n * A non-long request still in flight after `slow.stuckMs` turns `slowRequestActive` and the state to `slow`.\n */\n beginRequest(options: { long?: boolean } = {}): () => void {\n const id = ++this.nextRequestId;\n const entry: { start: number; stuck: boolean; timer: ReturnType<typeof setTimeout> | null } = { start: Date.now(), stuck: false, timer: null };\n this.inFlight.set(id, entry);\n this._inFlightCount.set(this.inFlight.size);\n if (!options.long) {\n entry.timer = setTimeout(() => {\n entry.timer = null;\n if (!this.inFlight.has(id)) return;\n entry.stuck = true;\n this._slowRequestActive.set(true);\n this.markSlow();\n }, this.cfg.slow.stuckMs);\n }\n return () => {\n const current = this.inFlight.get(id);\n if (!current) return;\n if (current.timer) clearTimeout(current.timer);\n this.inFlight.delete(id);\n this._inFlightCount.set(this.inFlight.size);\n this._slowRequestActive.set([...this.inFlight.values()].some(e => e.stuck));\n this.recompute();\n };\n }\n\n /**\n * Asks the probe URL once. Resolves true when it answered with ANY HTTP status (a 401 still proves the server is reachable).\n * Without a configured probe there is nothing to ask: the answer is what the browser says.\n */\n probeNow(): Promise<boolean> {\n const probe = this.cfg.probe;\n if (!probe) return Promise.resolve(this.browserOnline());\n if (this.probeInFlight) return this.probeInFlight;\n const run = this.runProbe(probe.url).then(ok => {\n if (ok) this.onProbeSuccess();\n return ok;\n }).finally(() => { this.probeInFlight = null; });\n this.probeInFlight = run;\n return run;\n }\n\n private async runProbe(url: string): Promise<boolean> {\n const win = this.win;\n if (!win || typeof win.fetch !== 'function') return false;\n const controller = typeof AbortController === 'function' ? new AbortController() : null;\n const timer = controller ? setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS) : null;\n try {\n await win.fetch(url, { method: 'GET', cache: 'no-store', credentials: 'same-origin', signal: controller?.signal });\n return true;\n } catch {\n return false;\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n\n private onProbeSuccess(): void {\n this.failures = [];\n if (this.unreachable()) this.clearUnreachable('probe');\n }\n\n private handleFailure(now: number): void {\n if (!this.browserOnline()) return;\n this.failures = this.failures.filter(t => now - t <= this.cfg.slow.windowMs);\n this.failures.push(now);\n if (this.failures.length >= 2) {\n this.setUnreachable();\n return;\n }\n if (this.cfg.probe) {\n void this.probeNow().then(ok => {\n if (!ok && this.browserOnline() && this.failures.length > 0) this.setUnreachable();\n });\n }\n }\n\n private setUnreachable(): void {\n if (this.unreachable()) return;\n this.unreachable.set(true);\n this.recompute();\n if (this.cfg.probe) {\n this.probeTimer = setInterval(() => { void this.probeNow(); }, this.cfg.probe.intervalMs);\n } else {\n // No probe to ask: assume it may be back after a while. The next failing request flips it straight back.\n this.optimisticTimer = setTimeout(() => {\n this.optimisticTimer = null;\n if (this.unreachable()) this.clearUnreachable('online');\n }, 15000);\n }\n }\n\n private clearUnreachable(trigger: RecoveryTrigger): void {\n if (!this.unreachable()) return;\n this.unreachable.set(false);\n this.stopRecoveryTimers();\n this.recompute();\n if (this.browserOnline()) this.recovered$.next(trigger);\n }\n\n private stopRecoveryTimers(): void {\n if (this.probeTimer) { clearInterval(this.probeTimer); this.probeTimer = null; }\n if (this.optimisticTimer) { clearTimeout(this.optimisticTimer); this.optimisticTimer = null; }\n }\n\n private markSlow(): void {\n this.slowFlag.set(true);\n if (this.quietTimer) clearTimeout(this.quietTimer);\n this.quietTimer = setTimeout(() => {\n this.quietTimer = null;\n this.slowFlag.set(false);\n this.slowTimes = [];\n this.recompute();\n }, this.cfg.slow.quietMs);\n this.recompute();\n }\n\n private scheduleOnline(): void {\n this.cancelOnlineTimer();\n this.onlineTimer = setTimeout(() => {\n this.onlineTimer = null;\n this.setBrowserOnline(true);\n }, ONLINE_DEBOUNCE_MS);\n }\n\n private cancelOnlineTimer(): void {\n if (this.onlineTimer) { clearTimeout(this.onlineTimer); this.onlineTimer = null; }\n }\n\n private setBrowserOnline(value: boolean): void {\n if (this.browserOnline() === value) return;\n this.browserOnline.set(value);\n if (value) {\n // Back from a real outage: forget the old failures and the old \"unreachable\" verdict; the next requests decide again.\n this.failures = [];\n if (this.unreachable()) { this.unreachable.set(false); this.stopRecoveryTimers(); }\n this.recompute();\n this.recovered$.next('online');\n } else {\n this.recompute();\n }\n }\n\n private syncFromNavigator(): void {\n if (!this.readNavigatorOnline()) { this.cancelOnlineTimer(); this.setBrowserOnline(false); }\n else if (!this.browserOnline()) this.scheduleOnline(); // a missed `online` event (sleep / wifi roaming): the browser says online again\n }\n\n private readNavigatorOnline(): boolean {\n try {\n return this.win?.navigator?.onLine !== false;\n } catch {\n return true;\n }\n }\n\n private recompute(): void {\n const next: ConnectionState = !this.browserOnline() ? 'offline'\n : this.unreachable() ? 'unreachable'\n : (this.slowFlag() || this._slowRequestActive()) ? 'slow'\n : 'online';\n if (next === this._state()) return;\n this._state.set(next);\n this._lastChangeAt.set(Date.now());\n }\n\n private teardown(): void {\n const win = this.win;\n if (win) {\n win.removeEventListener('online', this.onWindowOnline);\n win.removeEventListener('offline', this.onWindowOffline);\n win.removeEventListener('focus', this.onFocus);\n win.document?.removeEventListener('visibilitychange', this.onFocus);\n }\n this.cancelOnlineTimer();\n this.stopRecoveryTimers();\n if (this.quietTimer) { clearTimeout(this.quietTimer); this.quietTimer = null; }\n for (const e of this.inFlight.values()) if (e.timer) clearTimeout(e.timer);\n this.inFlight.clear();\n this.recovered$.complete();\n }\n}\n","import { Injectable, signal } from '@angular/core';\n\n/** Key used by callers that do not track which request a stale copy belongs to. */\nconst DEFAULT_KEY = '*';\n\n/** Tells the UI when what the user sees came from the device copy instead of the server. Tracked per request key (the cache key of the GET). */\n@Injectable({ providedIn: 'root' })\nexport class StaleDataService {\n private readonly _staleAt = signal<number | null>(null);\n private readonly entries = new Map<string, number>();\n /** `cachedAt` of the most recent cache-served GET that has not been replaced by a live answer for the same request; null = everything shown is live. */\n readonly staleAt = this._staleAt.asReadonly();\n\n markStale(cachedAt: number, key: string = DEFAULT_KEY): void {\n this.entries.set(key, cachedAt);\n this.publish();\n }\n\n /** A live answer arrived for this request: only its own stale mark goes away. */\n clearKey(key: string): void {\n if (this.entries.delete(key)) this.publish();\n }\n\n /** Forgets every stale mark. */\n clear(): void {\n if (this.entries.size === 0) return;\n this.entries.clear();\n this.publish();\n }\n\n private publish(): void {\n let latest: number | null = null;\n for (const v of this.entries.values()) if (latest === null || v > latest) latest = v;\n this._staleAt.set(latest);\n }\n}\n","import { Injectable, InjectionToken, inject } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { uiText } from '@forgeroute/ui';\nimport { OFFLINE_SUPPORT_CONFIG, resolveConfig } from '../config/offline-config';\n\nexport type MutationStatus = 'pending' | 'syncing' | 'failed' | 'conflict';\nexport type MutationErrorKind = 'conflict' | 'rejected' | 'auth' | 'server' | 'network' | 'timeout' | 'dependency';\n\nexport interface MutationError {\n kind: MutationErrorKind;\n status?: number;\n message: string;\n}\n\nexport interface OfflineMutation {\n id: string;\n seq: number;\n userId: string;\n /** The Idempotency-Key header of the original request; re-sent unchanged on every replay. */\n key: string;\n method: string;\n url: string;\n body: unknown;\n headers?: Record<string, string>;\n label: string;\n entityType?: string;\n entityId?: string;\n createdAt: number;\n status: MutationStatus;\n claimOwner?: string;\n claimAt?: number;\n attempts: number;\n nextAttemptAt?: number;\n lastError?: MutationError;\n /**\n * Set on a queued POST to a collection whose body had no id of its own: the client-made id handed out in the synthetic 202 (equals `id`).\n * Later saved changes may refer to it; after this row is delivered it is mapped to the server's real id (store `idmap`).\n */\n tempId?: string;\n /** Ids of the queued create rows whose temp id this change refers to (URL, query or body), set when the change was saved. */\n dependsOn?: string[];\n /** The temp ids behind `dependsOn`, same order (needed after the create row is gone to find its `idmap` entry). */\n dependsOnTempIds?: string[];\n}\n\nexport type NewMutation = Omit<OfflineMutation, 'seq'>;\n\nexport interface CachedEntry {\n /** Composite primary key: `<userId>::<url key>`. */\n key: string;\n urlKey: string;\n userId: string;\n data: unknown;\n cachedAt: number;\n expiresAt: number;\n}\n\n/** Maps a temp id to the real id the server gave the record. Key `<userId>:<tempId>`; kept 7 days. */\nexport interface IdMapEntry {\n key: string;\n userId: string;\n tempId: string;\n /** Always text; `numeric` tells that the server's id was a number (so a JSON body gets a number back). */\n realId: string;\n numeric?: boolean;\n createdAt: number;\n expiresAt: number;\n}\n\nexport const IDMAP_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport interface MutationCounts { pending: number; syncing: number; failed: number; conflict: number }\n\n/** A claim older than this in status `syncing` belongs to a tab that died mid-send and may be taken over. */\nexport const STALE_CLAIM_MS = 60_000;\nconst DB_VERSION = 2;\nconst OPEN_TIMEOUT_MS = 10_000;\nconst MAX_CACHE_ENTRIES = 500;\nconst EVICT_COUNT = 50;\nconst MUTATIONS = 'mutations';\nconst RESPONSES = 'responses';\nconst META = 'meta';\nconst IDMAP = 'idmap';\n\n/** IndexedDB, or null when the browser has none / refuses access (some privacy modes throw on the property read). */\nexport const OFFLINE_IDB_FACTORY = new InjectionToken<IDBFactory | null>('OFFLINE_IDB_FACTORY', {\n providedIn: 'root',\n factory: () => {\n try {\n return typeof indexedDB !== 'undefined' ? indexedDB : null;\n } catch {\n return null;\n }\n },\n});\n\n/** IndexedDB is missing, blocked or failed to open: nothing can be saved on this device. */\nexport class StorageUnavailableError extends Error {\n constructor(message: string = uiText('connection.storageNotAvailable')) {\n super(message);\n this.name = 'StorageUnavailableError';\n Object.setPrototypeOf(this, StorageUnavailableError.prototype);\n }\n}\n\n/** The user already has `maxQueue` saved changes. */\nexport class QueueFullError extends Error {\n constructor(readonly limit: number) {\n super(uiText('connection.queueFull', { limit }));\n this.name = 'QueueFullError';\n Object.setPrototypeOf(this, QueueFullError.prototype);\n }\n}\n\nexport function isQuotaError(e: unknown): boolean {\n const x = e as { name?: string; code?: number } | null;\n return !!x && (x.name === 'QuotaExceededError' || x.name === 'NS_ERROR_DOM_QUOTA_REACHED' || x.code === 22 || x.code === 1014);\n}\n\nfunction isInvalidState(e: unknown): boolean {\n const x = e as { name?: string } | null;\n return !!x && (x.name === 'InvalidStateError' || x.name === 'TransactionInactiveError');\n}\n\nconst RETRYABLE_FAILURES: readonly MutationErrorKind[] = ['server', 'network', 'timeout'];\n\n/** True when `claim` may take the row: pending, a failed row whose failure was transient, or a `syncing` row whose claim went stale. */\nexport function isClaimable(row: OfflineMutation, now: number): boolean {\n if (row.status === 'pending') return true;\n if (row.status === 'failed') return !!row.lastError && RETRYABLE_FAILURES.includes(row.lastError.kind);\n if (row.status === 'syncing') return now - (row.claimAt ?? 0) > STALE_CLAIM_MS;\n return false;\n}\n\ntype Stores = Record<string, IDBObjectStore>;\n\n/**\n * Thin promise wrapper over IndexedDB: the queued writes (`mutations`) and the per-user device copy of GET answers (`responses`).\n * Every row and cache entry carries its `userId`; reads and deletes are always scoped to a user. If IndexedDB cannot be used,\n * `available()` is false and callers must refuse to queue (never a fake success).\n */\n@Injectable({ providedIn: 'root' })\nexport class OfflineStore {\n private readonly cfg = resolveConfig(inject(OFFLINE_SUPPORT_CONFIG, { optional: true }) ?? { getUserId: () => null });\n private readonly factory = inject(OFFLINE_IDB_FACTORY);\n\n /** Emits after every write to the saved changes (add, claim, update, delete, clear); other tabs are told by OfflineSupportService. */\n readonly mutated$ = new Subject<void>();\n\n private db: IDBDatabase | null = null;\n private opening: Promise<IDBDatabase> | null = null;\n private initPromise: Promise<void> | null = null;\n private ready = false;\n\n available(): boolean {\n return this.ready;\n }\n\n /** Opens the database. Never rejects: on failure `available()` stays false (and a later `init()` tries again). */\n init(): Promise<void> {\n if (!this.initPromise) {\n this.initPromise = this.doInit().then(() => {\n if (!this.ready) this.initPromise = null;\n });\n }\n return this.initPromise;\n }\n\n private async doInit(): Promise<void> {\n if (!this.factory) { this.ready = false; return; }\n try {\n await this.getDb();\n this.ready = true;\n } catch {\n this.ready = false;\n return;\n }\n try { await this.purgeExpired(); } catch { /* cleanup is best effort */ }\n try { await this.purgeExpiredIdMap(); } catch { /* cleanup is best effort */ }\n }\n\n // ---- saved changes -------------------------------------------------------------------------------------------------------------\n\n /**\n * Saves a new change. If this user already has a row with the same idempotency `key` that row is returned instead (a double click that\n * re-used the key must not queue twice). Rejects with QueueFullError, StorageUnavailableError or the storage error (e.g. QuotaExceededError).\n */\n async addMutation(row: NewMutation): Promise<OfflineMutation> {\n await this.init();\n if (!this.ready) throw new StorageUnavailableError();\n const limit = this.cfg.maxQueue;\n const saved = await this.runTx<OfflineMutation>([MUTATIONS, META], 'readwrite', (s, resolveWith, abortWith) => {\n const existing = s[MUTATIONS].index('userId').getAll(row.userId) as IDBRequest<OfflineMutation[]>;\n existing.onsuccess = () => {\n const rows = existing.result;\n const dup = rows.find(r => r.key === row.key);\n if (dup) { resolveWith(dup); return; }\n if (rows.length >= limit) { abortWith(new QueueFullError(limit)); return; }\n const seqReq = s[META].get('seq') as IDBRequest<{ id: string; value: number } | undefined>;\n seqReq.onsuccess = () => {\n const seq = (seqReq.result?.value ?? 0) + 1;\n const full: OfflineMutation = { ...row, seq };\n s[META].put({ id: 'seq', value: seq });\n s[MUTATIONS].add(full);\n resolveWith(full);\n };\n };\n });\n this.mutated$.next();\n return saved;\n }\n\n /**\n * Atomically takes a row for sending: only if it is pending, failed with a transient error, or `syncing` with a claim older than 60 s.\n * Returns the claimed row, or null when it is gone or somebody else holds a live claim.\n */\n async claim(id: string, owner: string, now: number = Date.now()): Promise<OfflineMutation | null> {\n const claimed = await this.runTx<OfflineMutation | null>([MUTATIONS], 'readwrite', (s, resolveWith) => {\n resolveWith(null);\n const req = s[MUTATIONS].get(id) as IDBRequest<OfflineMutation | undefined>;\n req.onsuccess = () => {\n const row = req.result;\n if (!row || !isClaimable(row, now)) return;\n const next: OfflineMutation = { ...row, status: 'syncing', claimOwner: owner, claimAt: now };\n s[MUTATIONS].put(next);\n resolveWith(next);\n };\n });\n if (claimed) this.mutated$.next();\n return claimed;\n }\n\n /** Merges `patch` into the row (an `undefined` value removes the field). With `ifOwner` it only applies while that owner still holds the claim. */\n async update(id: string, patch: Partial<OfflineMutation>, opts: { ifOwner?: string } = {}): Promise<OfflineMutation | null> {\n const updated = await this.runTx<OfflineMutation | null>([MUTATIONS], 'readwrite', (s, resolveWith) => {\n resolveWith(null);\n const req = s[MUTATIONS].get(id) as IDBRequest<OfflineMutation | undefined>;\n req.onsuccess = () => {\n const row = req.result;\n if (!row) return;\n if (opts.ifOwner !== undefined && row.claimOwner !== opts.ifOwner) return;\n const merged = { ...row, ...patch, id: row.id, userId: row.userId } as OfflineMutation;\n const loose = merged as unknown as Record<string, unknown>;\n for (const k of Object.keys(loose)) if (loose[k] === undefined) delete loose[k];\n s[MUTATIONS].put(merged);\n resolveWith(merged);\n };\n });\n if (updated) this.mutated$.next();\n return updated;\n }\n\n /**\n * The user's \"Retry\": puts one row (any status) back to pending with 0 attempts, in ONE transaction so a claim taken in between cannot be overwritten.\n * Returns the reset row, or null when the row is missing, belongs to another user, or is being sent right now (live claim).\n */\n async resetForRetry(id: string, userId: string, now: number = Date.now()): Promise<OfflineMutation | null> {\n if (!(await this.ensureReady())) return null;\n const reset = await this.runTx<OfflineMutation | null>([MUTATIONS], 'readwrite', (s, resolveWith) => {\n resolveWith(null);\n const req = s[MUTATIONS].get(id) as IDBRequest<OfflineMutation | undefined>;\n req.onsuccess = () => {\n const row = req.result;\n if (!row || row.userId !== userId) return;\n if (row.status === 'syncing' && now - (row.claimAt ?? 0) <= STALE_CLAIM_MS) return;\n const next: OfflineMutation = { ...row, status: 'pending', attempts: 0 };\n delete next.nextAttemptAt;\n delete next.lastError;\n delete next.claimOwner;\n delete next.claimAt;\n s[MUTATIONS].put(next);\n resolveWith(next);\n };\n });\n if (reset) this.mutated$.next();\n return reset;\n }\n\n async get(id: string): Promise<OfflineMutation | null> {\n if (!(await this.ensureReady())) return null;\n return this.runTx<OfflineMutation | null>([MUTATIONS], 'readonly', (s, resolveWith) => {\n resolveWith(null);\n const req = s[MUTATIONS].get(id) as IDBRequest<OfflineMutation | undefined>;\n req.onsuccess = () => resolveWith(req.result ?? null);\n });\n }\n\n /** Deletes the row; with `ifOwner` only while that owner still holds the claim. Returns whether a row was deleted. */\n async delete(id: string, opts: { ifOwner?: string } = {}): Promise<boolean> {\n const deleted = await this.runTx<boolean>([MUTATIONS], 'readwrite', (s, resolveWith) => {\n resolveWith(false);\n const req = s[MUTATIONS].get(id) as IDBRequest<OfflineMutation | undefined>;\n req.onsuccess = () => {\n const row = req.result;\n if (!row) return;\n if (opts.ifOwner !== undefined && row.claimOwner !== opts.ifOwner) return;\n s[MUTATIONS].delete(id);\n resolveWith(true);\n };\n });\n if (deleted) this.mutated$.next();\n return deleted;\n }\n\n /** The user's rows in send order (seq, then createdAt). */\n async list(userId: string): Promise<OfflineMutation[]> {\n if (!(await this.ensureReady())) return [];\n const rows = await this.runTx<OfflineMutation[]>([MUTATIONS], 'readonly', (s, resolveWith) => {\n resolveWith([]);\n const req = s[MUTATIONS].index('userId').getAll(userId) as IDBRequest<OfflineMutation[]>;\n req.onsuccess = () => resolveWith(req.result);\n });\n return rows.filter(r => r.userId === userId).sort((a, b) => a.seq - b.seq || a.createdAt - b.createdAt);\n }\n\n async count(userId: string): Promise<number> {\n return (await this.list(userId)).length;\n }\n\n async countsByStatus(userId: string): Promise<MutationCounts> {\n const counts: MutationCounts = { pending: 0, syncing: 0, failed: 0, conflict: 0 };\n for (const r of await this.list(userId)) counts[r.status]++;\n return counts;\n }\n\n /** Deletes only the saved changes of this user (their device copy of GET answers stays). */\n async deleteMutations(userId: string): Promise<number> {\n if (!(await this.ensureReady())) return 0;\n const removed = await this.runTx<number>([MUTATIONS], 'readwrite', (s, resolveWith) => {\n resolveWith(0);\n const req = s[MUTATIONS].index('userId').getAll(userId) as IDBRequest<OfflineMutation[]>;\n req.onsuccess = () => {\n const rows = req.result.filter(r => r.userId === userId);\n for (const r of rows) s[MUTATIONS].delete(r.id);\n resolveWith(rows.length);\n };\n });\n if (removed > 0) this.mutated$.next();\n return removed;\n }\n\n /** Deletes this user's saved changes AND device copy. Never touches another user's data. */\n async clearUser(userId: string): Promise<void> {\n if (!(await this.ensureReady())) return;\n await this.deleteMutations(userId);\n await this.clearCache(userId);\n await this.deleteIdMap(userId);\n }\n\n /** Deletes everything for every user (rows, device copy). Only for an explicit, confirmed full reset. */\n async clearAll(): Promise<void> {\n if (!(await this.ensureReady())) return;\n await this.runTx<void>([MUTATIONS, RESPONSES, IDMAP], 'readwrite', (s) => {\n s[MUTATIONS].clear();\n s[RESPONSES].clear();\n s[IDMAP].clear();\n });\n this.mutated$.next();\n }\n\n // ---- temp id -> real id ---------------------------------------------------------------------------------------------------------\n\n /** Saves (or overwrites: a replayed create gives the same answer) the real id for a temp id. Rejects when the write fails. */\n async putIdMapping(userId: string, tempId: string, realId: string | number, now: number = Date.now()): Promise<IdMapEntry> {\n await this.init();\n if (!this.ready) throw new StorageUnavailableError();\n const entry: IdMapEntry = {\n key: `${userId}:${tempId}`, userId, tempId, realId: String(realId), createdAt: now, expiresAt: now + IDMAP_TTL_MS,\n ...(typeof realId === 'number' ? { numeric: true } : {}),\n };\n await this.runTx<void>([IDMAP], 'readwrite', s => { s[IDMAP].put(entry); });\n return entry;\n }\n\n /** The user's real id for `tempId`, or null (missing, expired, or somebody else's). */\n async getIdMapping(userId: string, tempId: string, now: number = Date.now()): Promise<IdMapEntry | null> {\n if (!(await this.ensureReady())) return null;\n const entry = await this.runTx<IdMapEntry | null>([IDMAP], 'readonly', (s, resolveWith) => {\n resolveWith(null);\n const req = s[IDMAP].get(`${userId}:${tempId}`) as IDBRequest<IdMapEntry | undefined>;\n req.onsuccess = () => resolveWith(req.result ?? null);\n });\n return entry && entry.userId === userId && entry.expiresAt > now ? entry : null;\n }\n\n /** All of the user's unexpired temp id mappings. */\n async listIdMap(userId: string, now: number = Date.now()): Promise<IdMapEntry[]> {\n if (!(await this.ensureReady())) return [];\n const all = await this.runTx<IdMapEntry[]>([IDMAP], 'readonly', (s, resolveWith) => {\n resolveWith([]);\n const req = s[IDMAP].index('userId').getAll(userId) as IDBRequest<IdMapEntry[]>;\n req.onsuccess = () => resolveWith(req.result);\n });\n return all.filter(e => e.userId === userId && e.expiresAt > now);\n }\n\n /** Deletes this user's temp id mappings only. */\n async deleteIdMap(userId: string): Promise<number> {\n if (!(await this.ensureReady())) return 0;\n return this.runTx<number>([IDMAP], 'readwrite', (s, resolveWith) => {\n resolveWith(0);\n const req = s[IDMAP].index('userId').getAll(userId) as IDBRequest<IdMapEntry[]>;\n req.onsuccess = () => {\n const mine = req.result.filter(e => e.userId === userId);\n for (const e of mine) s[IDMAP].delete(e.key);\n resolveWith(mine.length);\n };\n });\n }\n\n /** Removes mappings whose 7 days are over (every user). Returns how many. */\n async purgeExpiredIdMap(now: number = Date.now()): Promise<number> {\n return this.runTx<number>([IDMAP], 'readwrite', (s, resolveWith) => {\n resolveWith(0);\n const req = s[IDMAP].getAll() as IDBRequest<IdMapEntry[]>;\n req.onsuccess = () => {\n const old = req.result.filter(e => e.expiresAt <= now);\n for (const e of old) s[IDMAP].delete(e.key);\n resolveWith(old.length);\n };\n });\n }\n\n // ---- device copy of GET answers --------------------------------------------------------------------------------------------------\n\n /** Stores a GET answer for this user. Returns false when it could not be stored (never throws: the copy is optional). */\n async cacheResponse(key: string, userId: string, data: unknown, ttlMs: number, now: number = Date.now()): Promise<boolean> {\n if (!(await this.ensureReady())) return false;\n const entry: CachedEntry = { key: `${userId}::${key}`, urlKey: key, userId, data, cachedAt: now, expiresAt: now + ttlMs };\n const put = () => this.runTx<void>([RESPONSES], 'readwrite', s => { s[RESPONSES].put(entry); });\n try {\n await put();\n } catch (e) {\n if (!isQuotaError(e)) return false;\n try {\n await this.evictOldest(EVICT_COUNT, now);\n await put();\n } catch {\n return false;\n }\n }\n void this.enforceCacheLimit().catch(() => undefined);\n return true;\n }\n\n /** The user's stored answer for `key`, or null (missing, expired or belonging to somebody else). */\n async getCached(key: string, userId: string, now: number = Date.now()): Promise<CachedEntry | null> {\n if (!(await this.ensureReady())) return null;\n const composite = `${userId}::${key}`;\n const entry = await this.runTx<CachedEntry | null>([RESPONSES], 'readonly', (s, resolveWith) => {\n resolveWith(null);\n const req = s[RESPONSES].get(composite) as IDBRequest<CachedEntry | undefined>;\n req.onsuccess = () => resolveWith(req.result ?? null);\n }).catch(() => null);\n if (!entry || entry.userId !== userId) return null;\n if (entry.expiresAt <= now) {\n void this.runTx<void>([RESPONSES], 'readwrite', s => { s[RESPONSES].delete(composite); }).catch(() => undefined);\n return null;\n }\n return entry;\n }\n\n /** Removes this user's stored answers whose URL key starts with `prefix`. */\n async invalidatePrefix(prefix: string, userId: string): Promise<number> {\n if (!(await this.ensureReady())) return 0;\n return this.runTx<number>([RESPONSES], 'readwrite', (s, resolveWith) => {\n resolveWith(0);\n const req = s[RESPONSES].index('userId').getAll(userId) as IDBRequest<CachedEntry[]>;\n req.onsuccess = () => {\n const hits = req.result.filter(e => e.userId === userId && e.urlKey.startsWith(prefix));\n for (const e of hits) s[RESPONSES].delete(e.key);\n resolveWith(hits.length);\n };\n }).catch(() => 0);\n }\n\n /** Deletes the device copy of one user, or of everybody when `userId` is omitted. Saved changes are never touched. */\n async clearCache(userId?: string): Promise<void> {\n if (!(await this.ensureReady())) return;\n await this.runTx<void>([RESPONSES], 'readwrite', (s) => {\n if (userId === undefined) { s[RESPONSES].clear(); return; }\n const req = s[RESPONSES].index('userId').getAll(userId) as IDBRequest<CachedEntry[]>;\n req.onsuccess = () => { for (const e of req.result) if (e.userId === userId) s[RESPONSES].delete(e.key); };\n });\n }\n\n async purgeExpired(now: number = Date.now()): Promise<number> {\n return this.runTx<number>([RESPONSES], 'readwrite', (s, resolveWith) => {\n resolveWith(0);\n const req = s[RESPONSES].getAll() as IDBRequest<CachedEntry[]>;\n req.onsuccess = () => {\n const old = req.result.filter(e => e.expiresAt <= now);\n for (const e of old) s[RESPONSES].delete(e.key);\n resolveWith(old.length);\n };\n });\n }\n\n private async evictOldest(count: number, now: number): Promise<void> {\n await this.runTx<void>([RESPONSES], 'readwrite', (s) => {\n const req = s[RESPONSES].getAll() as IDBRequest<CachedEntry[]>;\n req.onsuccess = () => {\n const sorted = [...req.result].sort((a, b) => (a.expiresAt <= now ? 0 : 1) - (b.expiresAt <= now ? 0 : 1) || a.cachedAt - b.cachedAt);\n for (const e of sorted.slice(0, count)) s[RESPONSES].delete(e.key);\n };\n });\n }\n\n private async enforceCacheLimit(): Promise<void> {\n const total = await this.runTx<number>([RESPONSES], 'readonly', (s, resolveWith) => {\n resolveWith(0);\n const req = s[RESPONSES].count() as IDBRequest<number>;\n req.onsuccess = () => resolveWith(req.result);\n });\n if (total > MAX_CACHE_ENTRIES) await this.evictOldest(EVICT_COUNT, Date.now());\n }\n\n // ---- plumbing ------------------------------------------------------------------------------------------------------------------\n\n private async ensureReady(): Promise<boolean> {\n await this.init();\n return this.ready;\n }\n\n private getDb(): Promise<IDBDatabase> {\n if (this.db) return Promise.resolve(this.db);\n if (this.opening) return this.opening;\n const opening = this.openWithRetry().then(\n db => { this.db = db; this.opening = null; return db; },\n err => { this.opening = null; throw err; },\n );\n this.opening = opening;\n return opening;\n }\n\n private async openWithRetry(): Promise<IDBDatabase> {\n try {\n return await this.openOnce();\n } catch (first) {\n // Safari sometimes fails the very first open of a fresh profile; one more try is what the browsers recommend.\n if (first instanceof StorageUnavailableError || (first as { name?: string } | null)?.name === 'SecurityError') throw first;\n return this.openOnce();\n }\n }\n\n private openOnce(): Promise<IDBDatabase> {\n const factory = this.factory;\n return new Promise<IDBDatabase>((resolve, reject) => {\n if (!factory) { reject(new StorageUnavailableError()); return; }\n let req: IDBOpenDBRequest;\n try {\n req = factory.open(this.cfg.dbName, DB_VERSION);\n } catch (e) {\n reject(e);\n return;\n }\n const timer = setTimeout(() => reject(new StorageUnavailableError(uiText('connection.storageTimeout'))), OPEN_TIMEOUT_MS);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(MUTATIONS)) {\n const m = db.createObjectStore(MUTATIONS, { keyPath: 'id' });\n m.createIndex('userId', 'userId');\n m.createIndex('status', 'status');\n m.createIndex('createdAt', 'createdAt');\n }\n if (!db.objectStoreNames.contains(RESPONSES)) {\n const r = db.createObjectStore(RESPONSES, { keyPath: 'key' });\n r.createIndex('userId', 'userId');\n r.createIndex('expiresAt', 'expiresAt');\n }\n if (!db.objectStoreNames.contains(META)) db.createObjectStore(META, { keyPath: 'id' });\n // Version 2 only ADDS this store; every store and row from version 1 is left exactly as it is.\n if (!db.objectStoreNames.contains(IDMAP)) {\n const i = db.createObjectStore(IDMAP, { keyPath: 'key' });\n i.createIndex('userId', 'userId');\n i.createIndex('expiresAt', 'expiresAt');\n }\n };\n req.onsuccess = () => {\n clearTimeout(timer);\n const db = req.result;\n // Another tab wants to upgrade, or the browser closed the connection: drop it so the next call reopens.\n db.onversionchange = () => { db.close(); if (this.db === db) this.db = null; };\n db.onclose = () => { if (this.db === db) this.db = null; };\n resolve(db);\n };\n req.onerror = () => { clearTimeout(timer); reject(req.error ?? new StorageUnavailableError()); };\n req.onblocked = () => { /* waits for the other connection to close; the timeout above ends the wait */ };\n });\n }\n\n private async runTx<T>(\n names: string[],\n mode: IDBTransactionMode,\n work: (stores: Stores, resolveWith: (v: T) => void, abortWith: (e: unknown) => void) => void,\n retried = false,\n ): Promise<T> {\n const db = await this.getDb();\n let tx: IDBTransaction;\n try {\n tx = db.transaction(names, mode);\n } catch (e) {\n if (!retried && isInvalidState(e)) {\n if (this.db === db) this.db = null;\n return this.runTx(names, mode, work, true);\n }\n throw e;\n }\n return new Promise<T>((resolve, reject) => {\n let result: T | undefined;\n let failure: unknown;\n let failed = false;\n const abortWith = (e: unknown) => {\n failed = true;\n failure = e;\n try { tx.abort(); } catch { /* already finished */ }\n };\n tx.oncomplete = () => resolve(result as T);\n tx.onabort = () => reject(failed ? failure : tx.error ?? new Error(uiText('connection.transactionAborted')));\n const stores: Stores = {};\n try {\n for (const n of names) stores[n] = tx.objectStore(n);\n work(stores, v => { result = v; }, abortWith);\n } catch (e) {\n abortWith(e);\n }\n });\n }\n}\n","/** Why an offline write was refused. */\nexport type OfflineBlockedReason = 'blocked' | 'upload' | 'storage' | 'not-allowed';\n\n/**\n * A write that cannot be saved on the device while offline. `message` is plain words for the user (from UiLabelsService or the app's\n * `blockOffline[].message`). Nothing was queued and nothing was sent.\n */\nexport class OfflineBlockedError extends Error {\n readonly offlineBlocked = true;\n readonly status = 0;\n readonly reason: OfflineBlockedReason;\n constructor(message: string, reason: OfflineBlockedReason = 'blocked') {\n super(message);\n this.name = 'OfflineBlockedError';\n this.reason = reason;\n Object.setPrototypeOf(this, OfflineBlockedError.prototype);\n }\n}\n\n/** The client gave up waiting. For a write `outcomeUnknown` is true: the server may still have handled it. */\nexport class RequestTimeoutError extends Error {\n readonly timeout = true;\n readonly status = 0;\n readonly method: string;\n readonly url: string;\n readonly outcomeUnknown: boolean;\n constructor(message: string, method: string, url: string, outcomeUnknown: boolean) {\n super(message);\n this.name = 'RequestTimeoutError';\n this.method = method;\n this.url = url;\n this.outcomeUnknown = outcomeUnknown;\n Object.setPrototypeOf(this, RequestTimeoutError.prototype);\n }\n}\n\nexport function isOfflineBlockedError(e: unknown): e is OfflineBlockedError {\n return e instanceof OfflineBlockedError || (typeof e === 'object' && e !== null && (e as { offlineBlocked?: unknown }).offlineBlocked === true);\n}\n\nexport function isRequestTimeoutError(e: unknown): e is RequestTimeoutError {\n return e instanceof RequestTimeoutError || (typeof e === 'object' && e !== null && (e as { timeout?: unknown }).timeout === true && (e as { status?: unknown }).status === 0);\n}\n","import { lastSegmentIsId } from '../config/offline-config';\n\n/**\n * Temporary ids: a record created offline gets a client-made id (the `id` of the synthetic 202). Later saved changes may point at it (URL path\n * segment, query value, or a string value anywhere inside the JSON body). These helpers find those references and, at send time, swap them for the\n * server's real id. Matching is EXACT equality of a whole segment / value: a string that merely contains the temp id is never touched, and keys are\n * never touched. Nothing here mutates its input.\n */\n\n/** True when a JSON object body carries an `id` of its own (not null / undefined): the app chose the id, so nothing needs mapping. */\nexport function bodyHasOwnId(body: unknown): boolean {\n if (body === null || typeof body !== 'object' || Array.isArray(body)) return false;\n const id = (body as Record<string, unknown>)['id'];\n return id !== undefined && id !== null;\n}\n\n/** A POST to a collection (last path segment is not an id) whose body has no id of its own: the server will invent the id. */\nexport function isTempIdCreate(method: string, url: string, body: unknown): boolean {\n return method.toUpperCase() === 'POST' && !lastSegmentIsId(url) && !bodyHasOwnId(body);\n}\n\ninterface UrlParts { path: string; query: string | null; hash: string }\n\nfunction splitUrl(url: string): UrlParts {\n const h = url.indexOf('#');\n const hash = h >= 0 ? url.slice(h) : '';\n const noHash = h >= 0 ? url.slice(0, h) : url;\n const q = noHash.indexOf('?');\n return { path: q >= 0 ? noHash.slice(0, q) : noHash, query: q >= 0 ? noHash.slice(q + 1) : null, hash };\n}\n\nfunction decode(s: string): string {\n try { return decodeURIComponent(s); } catch { return s; }\n}\n\nfunction walkStrings(value: unknown, visit: (s: string) => void): void {\n if (typeof value === 'string') { visit(value); return; }\n if (Array.isArray(value)) { for (const v of value) walkStrings(v, visit); return; }\n if (value !== null && typeof value === 'object') { for (const v of Object.values(value as Record<string, unknown>)) walkStrings(v, visit); }\n}\n\n/** Every string that could be a reference: path segments, query values (decoded) and body string values. Keys are not included. */\nfunction referenceStrings(url: string, body: unknown): string[] {\n const out: string[] = [];\n const parts = splitUrl(url);\n for (const seg of parts.path.split('/')) if (seg !== '') out.push(decode(seg));\n if (parts.query !== null) {\n for (const pair of parts.query.split('&')) {\n const eq = pair.indexOf('=');\n if (eq >= 0) out.push(decode(pair.slice(eq + 1)));\n }\n }\n walkStrings(body, s => out.push(s));\n return out;\n}\n\n/** The candidate temp ids that the URL / body of a request refers to, each once, in order of first appearance. */\nexport function findTempIds(url: string, body: unknown, candidates: ReadonlySet<string> | ReadonlyMap<string, unknown>): string[] {\n const found: string[] = [];\n if (candidates.size === 0) return found;\n for (const s of referenceStrings(url, body)) {\n if (candidates.has(s) && !found.includes(s)) found.push(s);\n }\n return found;\n}\n\nfunction rewriteValue(value: unknown, map: ReadonlyMap<string, string | number>): unknown {\n if (typeof value === 'string') return map.has(value) ? map.get(value) : value;\n if (Array.isArray(value)) return value.map(v => rewriteValue(v, map));\n if (value !== null && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n // defineProperty so an own \"__proto__\" key from JSON.parse is kept as data instead of being swallowed by the prototype setter.\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n Object.defineProperty(out, k, { value: rewriteValue(v, map), enumerable: true, writable: true, configurable: true });\n }\n return out;\n }\n return value;\n}\n\n/**\n * The request as it must be sent now: every path segment / query value / body string value that EQUALS a temp id in `map` is replaced by the real id\n * (in the URL always as text, URL-encoded; in the body as given: a number stays a number). Returns new values; the inputs are not modified.\n */\nexport function rewriteTempIds(url: string, body: unknown, map: ReadonlyMap<string, string | number>): { url: string; body: unknown } {\n if (map.size === 0) return { url, body };\n const parts = splitUrl(url);\n const path = parts.path.split('/').map(seg => {\n const d = decode(seg);\n return map.has(d) ? encodeURIComponent(String(map.get(d))) : seg;\n }).join('/');\n let query = parts.query;\n if (query !== null) {\n query = query.split('&').map(pair => {\n const eq = pair.indexOf('=');\n if (eq < 0) return pair;\n const d = decode(pair.slice(eq + 1));\n return map.has(d) ? `${pair.slice(0, eq)}=${encodeURIComponent(String(map.get(d)))}` : pair;\n }).join('&');\n }\n return { url: path + (query !== null ? `?${query}` : '') + parts.hash, body: rewriteValue(body, map) };\n}\n","import { HttpClient, HttpErrorResponse } from '@angular/common/http';\nimport { Injectable, InjectionToken, inject, signal } from '@angular/core';\nimport { Subject, firstValueFrom } from 'rxjs';\nimport { ToastService, UiLabelsService } from '@forgeroute/ui';\nimport {\n IDEMPOTENCY_HEADER, OFFLINE_REPLAY_HEADER, OFFLINE_SUPPORT_CONFIG, resolveConfig,\n} from '../config/offline-config';\nimport { generateUuid } from '../config/uuid';\nimport { isRequestTimeoutError } from '../http/errors';\nimport { ConnectionStateService } from '../state/connection-state.service';\nimport { IdMapEntry, MutationError, MutationErrorKind, OfflineMutation, OfflineStore } from '../store/offline-store';\nimport { findTempIds, rewriteTempIds } from '../store/temp-ids';\n\nexport type ReplayTrigger = 'start' | 'online' | 'probe' | 'manual';\n\nexport interface ReplayResult {\n synced: number;\n failed: number;\n conflicts: number;\n blockedByAuth: boolean;\n}\n\n/** `navigator.locks`, or null when the browser has none (older Safari) - the replayer then falls back to an in-tab mutex plus a localStorage lease. */\nexport const OFFLINE_LOCK_MANAGER = new InjectionToken<LockManager | null>('OFFLINE_LOCK_MANAGER', {\n providedIn: 'root',\n factory: () => {\n try {\n return typeof navigator !== 'undefined' && navigator.locks && typeof navigator.locks.request === 'function' ? navigator.locks : null;\n } catch {\n return null;\n }\n },\n});\n\nexport type LeaseStorage = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;\n\n/** Storage for the lock-less fallback lease: ONE small key holding `{ owner, until }`, never any data. */\nexport const OFFLINE_LEASE_STORAGE = new InjectionToken<LeaseStorage | null>('OFFLINE_LEASE_STORAGE', {\n providedIn: 'root',\n factory: () => {\n try {\n return typeof localStorage !== 'undefined' ? localStorage : null;\n } catch {\n return null;\n }\n },\n});\n\nexport const LEASE_TTL_MS = 30_000;\nconst LEASE_REFRESH_MS = 10_000;\nconst BACKOFF_BASE_MS = 2000;\nconst BACKOFF_CAP_MS = 5 * 60_000;\n\n/** Wait before the next automatic try after `attempts` failed sends: 2 s, 4 s, 8 s ... capped at 5 minutes. */\nexport function backoffMs(attempts: number): number {\n return Math.min(BACKOFF_BASE_MS * 2 ** Math.max(0, attempts - 1), BACKOFF_CAP_MS);\n}\n\ntype Outcome =\n | { action: 'ok'; text?: string }\n | { action: 'dependency' }\n | { action: 'conflict'; status: number }\n | { action: 'rejected'; status: number }\n | { action: 'auth'; status: number }\n | { action: 'retry'; kind: Extract<MutationErrorKind, 'server' | 'network' | 'timeout'>; status: number };\n\n/** Maps what a replayed request ended with to what happens to its row. Anything unknown stays retryable: only a real 4xx answer is final. */\nexport function classifyReplay(err: unknown): Outcome {\n if (isRequestTimeoutError(err)) return { action: 'retry', kind: 'timeout', status: 0 };\n if (!(err instanceof HttpErrorResponse)) return { action: 'retry', kind: 'network', status: 0 };\n const s = err.status;\n if (s >= 200 && s < 300) return { action: 'ok' };\n if (s === 0) return { action: 'retry', kind: 'network', status: 0 };\n if (s === 401 || s === 403) return { action: 'auth', status: s };\n if (s === 409) return { action: 'conflict', status: s };\n if (s === 408 || s === 429) return { action: 'retry', kind: 'server', status: s };\n if (s >= 400 && s < 500) return { action: 'rejected', status: s };\n return { action: 'retry', kind: 'server', status: s };\n}\n\n/**\n * Sends the signed-in user's saved changes, oldest first, when the connection is back. Safe by construction:\n * - one run at a time per browser (navigator.locks, or in-tab mutex + localStorage lease), and every row is `claim`ed atomically in IndexedDB;\n * - the row's own Idempotency-Key is re-sent unchanged, so even a crash after the server answered cannot create a second record;\n * - 401/403 never touch a row, a permanent 4xx marks the row failed (only the user's Retry sends it again), nothing is ever deleted except\n * after a 2xx or the user's own Discard.\n */\n@Injectable({ providedIn: 'root' })\nexport class OfflineReplayer {\n private readonly cfg = resolveConfig(inject(OFFLINE_SUPPORT_CONFIG, { optional: true }) ?? { getUserId: () => null });\n private readonly store = inject(OfflineStore);\n private readonly http = inject(HttpClient);\n private readonly connection = inject(ConnectionStateService);\n private readonly toast = inject(ToastService);\n private readonly labels = inject(UiLabelsService);\n private readonly locks = inject(OFFLINE_LOCK_MANAGER);\n private readonly leaseStorage = inject(OFFLINE_LEASE_STORAGE);\n\n private readonly owner = generateUuid();\n private running: Promise<ReplayResult | null> | null = null;\n\n private readonly _syncing = signal(false);\n private readonly _pendingCount = signal(0);\n private readonly _failedCount = signal(0);\n private readonly _conflictCount = signal(0);\n private readonly _lastSyncAt = signal<number | null>(null);\n private readonly _syncTick = signal(0);\n private readonly _authBlocked = signal(false);\n private readonly _lastResult = signal<ReplayResult | null>(null);\n\n readonly syncing = this._syncing.asReadonly();\n /** Rows waiting to be sent (pending + being sent right now). */\n readonly pendingCount = this._pendingCount.asReadonly();\n readonly failedCount = this._failedCount.asReadonly();\n readonly conflictCount = this._conflictCount.asReadonly();\n readonly lastSyncAt = this._lastSyncAt.asReadonly();\n /** Bumped when a run changed server data (a row was delivered): open pages refresh on it. */\n readonly syncTick = this._syncTick.asReadonly();\n /** True after a 401/403 stopped a run: the rows are safe and wait for the user to sign in again. */\n readonly authBlocked = this._authBlocked.asReadonly();\n readonly lastResult = this._lastResult.asReadonly();\n /** Emits at the end of every run that did something. */\n readonly runCompleted$ = new Subject<{ trigger: ReplayTrigger; result: ReplayResult; changed: boolean }>();\n\n /** Sends everything that is due. Concurrent calls in this tab share one run; another tab's run makes this resolve null. */\n replayAll(trigger: ReplayTrigger): Promise<ReplayResult | null> {\n if (this.running) return this.running;\n return this.start(() => this.run(trigger, null));\n }\n\n /** Puts one row (any status, conflict and failed included) back to pending with 0 attempts and sends only that row. */\n async retryOne(id: string): Promise<ReplayResult | null> {\n await this.store.init();\n const userId = this.cfg.getUserId();\n if (!userId) return null;\n // One atomic step in the store: a row another run is sending right now (live claim) is left alone.\n const reset = await this.store.resetForRetry(id, userId);\n if (!reset) return null;\n if (this.running) await this.running;\n return this.start(() => this.run('manual', id));\n }\n\n /** Deletes one row of the signed-in user (the user chose to discard it). */\n async discard(id: string): Promise<void> {\n await this.store.init();\n const userId = this.cfg.getUserId();\n if (!userId) return;\n const row = await this.store.get(id);\n if (!row || row.userId !== userId) return;\n await this.store.delete(id);\n await this.refreshCounts();\n }\n\n /** Deletes all rows of the signed-in user (the user chose to discard everything). */\n async discardAll(): Promise<void> {\n await this.store.init();\n const userId = this.cfg.getUserId();\n if (!userId) return;\n await this.store.deleteMutations(userId);\n this._authBlocked.set(false);\n await this.refreshCounts();\n }\n\n /** Re-reads the counts for the signed-in user (zeros when nobody is signed in or storage is unavailable). */\n async refreshCounts(): Promise<void> {\n const userId = this.cfg.getUserId();\n if (!userId || !this.store.available()) {\n this._pendingCount.set(0); this._failedCount.set(0); this._conflictCount.set(0);\n return;\n }\n const c = await this.store.countsByStatus(userId);\n if (this.cfg.getUserId() !== userId) return;\n this._pendingCount.set(c.pending + c.syncing);\n this._failedCount.set(c.failed);\n this._conflictCount.set(c.conflict);\n }\n\n clearAuthBlocked(): void {\n this._authBlocked.set(false);\n }\n\n /** Another tab delivered rows: open pages in this tab must refresh too. */\n noteRemoteSync(): void {\n this._syncTick.update(n => n + 1);\n }\n\n /** Forgets everything about the previous user (counts, last result); the stored rows are not touched. */\n resetView(): void {\n this._pendingCount.set(0); this._failedCount.set(0); this._conflictCount.set(0);\n this._lastResult.set(null); this._authBlocked.set(false);\n }\n\n // ---- run -----------------------------------------------------------------------------------------------------------------------\n\n private start(job: () => Promise<ReplayResult | null>): Promise<ReplayResult | null> {\n const p = this.exclusive(job).finally(() => { if (this.running === p) this.running = null; });\n this.running = p;\n return p;\n }\n\n private async exclusive(job: () => Promise<ReplayResult | null>): Promise<ReplayResult | null> {\n const name = `forgeroute-offline-replay:${this.cfg.dbName}`;\n if (this.locks) {\n try {\n return await this.locks.request(name, { ifAvailable: true }, async lock => (lock ? job() : null)) as ReplayResult | null;\n } catch (e) {\n // Opaque origins / some embedded views refuse the Web Locks API: use the lease instead. Errors of the job itself are not swallowed.\n if (!(e instanceof DOMException) || (e.name !== 'SecurityError' && e.name !== 'NotSupportedError')) throw e;\n }\n }\n return this.withLease(job);\n }\n\n private leaseKey(): string {\n return `forgeroute-offline-lease:${this.cfg.dbName}`;\n }\n\n private readLease(): { owner?: string; until?: number } | null {\n try {\n const parsed = JSON.parse(this.leaseStorage?.getItem(this.leaseKey()) ?? 'null') as { owner?: string; until?: number } | null;\n return parsed && typeof parsed === 'object' ? parsed : null;\n } catch {\n return null; // a corrupt value counts as no lease\n }\n }\n\n private tryAcquireLease(): boolean {\n const storage = this.leaseStorage;\n if (!storage) return true; // no shared storage: the in-tab mutex above is all there is\n try {\n const now = Date.now();\n const current = this.readLease();\n if (current && current.owner !== this.owner && typeof current.until === 'number' && current.until > now) return false;\n storage.setItem(this.leaseKey(), JSON.stringify({ owner: this.owner, until: now + LEASE_TTL_MS }));\n return this.readLease()?.owner === this.owner;\n } catch {\n return true; // storage refuses writes (quota / privacy mode): run anyway, the row claims still prevent double sending\n }\n }\n\n private releaseLease(): void {\n const storage = this.leaseStorage;\n if (!storage) return;\n try {\n if (this.readLease()?.owner === this.owner) storage.removeItem(this.leaseKey());\n } catch { /* nothing to release */ }\n }\n\n private async withLease(job: () => Promise<ReplayResult | null>): Promise<ReplayResult | null> {\n if (!this.tryAcquireLease()) return null;\n const heartbeat = this.leaseStorage ? setInterval(() => this.tryAcquireLease(), LEASE_REFRESH_MS) : null;\n try {\n return await job();\n } finally {\n if (heartbeat) clearInterval(heartbeat);\n this.releaseLease();\n }\n }\n\n private async run(trigger: ReplayTrigger, onlyId: string | null): Promise<ReplayResult | null> {\n await this.store.init();\n const userId = this.cfg.getUserId();\n if (!userId || !this.store.available()) return null;\n if (trigger !== 'manual' && this.connection.isOffline()) return null;\n\n this._syncing.set(true);\n const result: ReplayResult = { synced: 0, failed: 0, conflicts: 0, blockedByAuth: false };\n let attempted = 0;\n let changed = false;\n try {\n const all = await this.store.list(userId);\n const rows = onlyId ? all.filter(r => r.id === onlyId) : all;\n // Temp ids: which create rows are still queued, and what the server already answered for finished ones.\n const idMap = new Map<string, IdMapEntry>();\n for (const e of await this.store.listIdMap(userId)) idMap.set(e.tempId, e);\n const createsByTemp = new Map<string, OfflineMutation>();\n for (const r of all) if (r.tempId) createsByTemp.set(r.tempId, r);\n const blocked = new Set<string>();\n const entityOf = (r: OfflineMutation) => (r.entityType && r.entityId ? `${r.entityType}::${r.entityId}` : null);\n if (!onlyId) {\n // An older change that is stuck (failed / conflict) holds back later changes to the same record: an older PUT must never overwrite a newer one.\n for (const r of rows) {\n const k = entityOf(r);\n if (k && (r.status === 'failed' || r.status === 'conflict')) blocked.add(k);\n }\n }\n for (const row of rows) {\n if (this.cfg.getUserId() !== userId) break; // the user switched mid-run: the rest belongs to somebody else's session\n const ek = entityOf(row);\n if (!onlyId) {\n if (row.status === 'failed' || row.status === 'conflict') continue;\n if (ek && blocked.has(ek)) continue;\n if (row.status === 'pending' && row.nextAttemptAt !== undefined && row.nextAttemptAt > Date.now() && trigger !== 'manual') {\n if (ek) blocked.add(ek);\n continue;\n }\n }\n // A change that points at a record created offline waits for that create (never sent while it is still queued), and is failed without\n // being sent when the create can no longer provide the real id.\n const dependency = await this.checkDependencies(row, userId, idMap, createsByTemp);\n if (dependency === 'hold') { if (ek) blocked.add(ek); continue; }\n const claimed = await this.store.claim(row.id, this.owner);\n if (!claimed) { if (ek) blocked.add(ek); continue; }\n if (this.cfg.getUserId() !== userId) {\n // The session changed while the claim was being made: never send this row under another user's token. Give the claim back.\n await this.store.update(claimed.id, { status: 'pending', claimOwner: undefined, claimAt: undefined }, { ifOwner: this.owner });\n break;\n }\n attempted++;\n const outcome: Outcome = dependency === 'fail' ? { action: 'dependency' } : await this.send(claimed, idMap);\n const stop = await this.apply(claimed, outcome, result, idMap);\n if (outcome.action === 'ok') changed = true;\n if (outcome.action !== 'ok' && ek) blocked.add(ek);\n if (stop) break;\n }\n } finally {\n this._syncing.set(false);\n }\n await this.refreshCounts();\n if (result.synced > 0 && !result.blockedByAuth) this._authBlocked.set(false);\n if (attempted > 0) {\n this._lastResult.set(result);\n if (result.synced + result.failed + result.conflicts > 0) this._lastSyncAt.set(Date.now());\n }\n if (changed) this._syncTick.update(n => n + 1);\n this.announce(result);\n if (attempted > 0) this.runCompleted$.next({ trigger, result, changed });\n return result;\n }\n\n /**\n * 'hold': a create this change points at is still pending / being sent (or waiting out a backoff): do not send it yet.\n * 'fail': a create it points at failed / conflicted, was discarded, or answered without a readable id: it can never be sent correctly.\n * Reads the store fresh for every create that has no mapping yet, so a run in another tab that just finished the create is seen.\n */\n private async checkDependencies(\n row: OfflineMutation, userId: string, idMap: Map<string, IdMapEntry>, createsByTemp: ReadonlyMap<string, OfflineMutation>,\n ): Promise<'ok' | 'hold' | 'fail'> {\n const deps = new Map<string, string | undefined>(); // temp id -> create row id\n (row.dependsOn ?? []).forEach((rowId, i) => {\n const tempId = row.dependsOnTempIds?.[i] ?? [...createsByTemp.values()].find(c => c.id === rowId)?.tempId;\n deps.set(tempId ?? `\\u0000${rowId}`, rowId);\n });\n for (const t of findTempIds(row.url, row.body, createsByTemp)) {\n if (t !== row.tempId && !deps.has(t)) deps.set(t, createsByTemp.get(t)?.id);\n }\n let hold = false;\n for (const [tempId, creatorId] of deps) {\n if (idMap.has(tempId)) continue;\n let creator = creatorId ? await this.store.get(creatorId) : null;\n if (creator && creator.userId !== userId) creator = null;\n if (creator) {\n if (creator.status === 'pending' || creator.status === 'syncing') hold = true;\n else return 'fail';\n continue;\n }\n const late = await this.store.getIdMapping(userId, tempId);\n if (late) { idMap.set(tempId, late); continue; }\n return 'fail';\n }\n return hold ? 'hold' : 'ok';\n }\n\n private async send(row: OfflineMutation, idMap: ReadonlyMap<string, IdMapEntry>): Promise<Outcome> {\n try {\n const real = new Map<string, string | number>();\n for (const [tempId, e] of idMap) real.set(tempId, e.numeric && String(Number(e.realId)) === e.realId ? Number(e.realId) : e.realId);\n const target = rewriteTempIds(row.url, row.body, real);\n const response = await firstValueFrom(this.http.request(row.method, target.url, {\n body: target.body,\n headers: { ...(row.headers ?? {}), [IDEMPOTENCY_HEADER]: row.key, [OFFLINE_REPLAY_HEADER]: '1' },\n observe: 'response',\n responseType: 'text',\n }));\n return { action: 'ok', text: typeof response.body === 'string' ? response.body : undefined };\n } catch (err) {\n return classifyReplay(err);\n }\n }\n\n /** Stores the outcome on the row (only while we still own the claim). Returns true when the run must stop. */\n private async apply(row: OfflineMutation, outcome: Outcome, result: ReplayResult, idMap: Map<string, IdMapEntry>): Promise<boolean> {\n const owner = { ifOwner: this.owner };\n const release = { claimOwner: undefined, claimAt: undefined };\n const err = (kind: MutationErrorKind, status: number | undefined, key: Parameters<UiLabelsService['t']>[0]): MutationError =>\n ({ kind, ...(status !== undefined ? { status } : {}), message: this.labels.t(key) });\n\n switch (outcome.action) {\n case 'ok':\n try {\n // Order matters: the mapping is saved BEFORE the create row is deleted. If this tab dies in between, the create is replayed with the same\n // Idempotency-Key (same answer, same real id) and the mapping is simply written again.\n if (row.tempId) {\n const real = this.readCreatedId(row, outcome.text);\n if (real !== undefined) idMap.set(row.tempId, await this.store.putIdMapping(row.userId, row.tempId, real));\n }\n await this.store.delete(row.id, owner);\n } catch {\n // Storage trouble after the server accepted it: give the row back (it is replayed safely with the same key) and stop this run.\n await this.store.update(row.id, { status: 'pending', ...release }, owner).catch(() => null);\n return true;\n }\n result.synced++;\n this._authBlocked.set(false);\n return false;\n case 'dependency':\n // Never sent: the id it points at is unknown. attempts is not counted; only the user's Retry (once the create is healthy) sends it.\n await this.store.update(row.id, { status: 'failed', ...release, nextAttemptAt: undefined, lastError: err('dependency', undefined, 'connection.error.dependency') }, owner);\n result.failed++;\n return false;\n case 'conflict':\n await this.store.update(row.id, { status: 'conflict', ...release, attempts: row.attempts + 1, lastError: err('conflict', outcome.status, 'connection.error.conflict') }, owner);\n result.conflicts++;\n return false;\n case 'rejected':\n await this.store.update(row.id, { status: 'failed', ...release, attempts: row.attempts + 1, lastError: err('rejected', outcome.status, 'connection.error.rejected') }, owner);\n result.failed++;\n return false;\n case 'auth':\n // The token is stale or the permission is gone: nothing is wrong with the data. Keep the row exactly as it was and stop.\n await this.store.update(row.id, { status: 'pending', ...release, lastError: err('auth', outcome.status, 'connection.authBlocked') }, owner);\n this._authBlocked.set(true);\n result.blockedByAuth = true;\n return true;\n case 'retry': {\n const message = outcome.kind === 'network' ? 'connection.error.network' : outcome.kind === 'timeout' ? 'connection.error.timeout' : 'connection.error.server';\n const lastError = err(outcome.kind, outcome.status || undefined, message);\n const stillOffline = outcome.status === 0 && this.connection.isOffline();\n if (stillOffline) {\n await this.store.update(row.id, { status: 'pending', ...release, lastError }, owner);\n return true;\n }\n const attempts = row.attempts + 1;\n if (attempts >= this.cfg.maxAttempts) {\n await this.store.update(row.id, { status: 'failed', ...release, attempts, nextAttemptAt: undefined, lastError }, owner);\n result.failed++;\n } else {\n await this.store.update(row.id, { status: 'pending', ...release, attempts, nextAttemptAt: Date.now() + backoffMs(attempts), lastError }, owner);\n }\n return outcome.kind !== 'server'; // no answer at all: still offline, stop; a 5xx / 429 only holds back this record\n }\n }\n }\n\n /** The server's id for a created record: the answer text parsed as JSON (undefined when empty / not JSON) run through `config.createdId`. */\n private readCreatedId(row: OfflineMutation, text: string | undefined): string | number | undefined {\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = undefined; }\n return this.cfg.createdId(body, row);\n }\n\n private announce(result: ReplayResult): void {\n const attention = result.failed + result.conflicts;\n if (attention > 0) this.toast.warning(this.labels.t(attention === 1 ? 'connection.needAttention.one' : 'connection.needAttention.other', { count: attention }));\n else if (result.synced > 0 && !result.blockedByAuth) this.toast.success(this.labels.t('connection.synced'));\n }\n}\n","import { HttpContextToken } from '@angular/common/http';\n\n/**\n * Per-request client timeout in ms, set through `HttpContext`: `http.get(url, { context: new HttpContext().set(REQUEST_TIMEOUT_MS, 120000) })`.\n * `null` (default) = the configured read / write / long timeout. A value of 0 or less switches the timeout off for that request.\n */\nexport const REQUEST_TIMEOUT_MS = new HttpContextToken<number | null>(() => null);\n","import { HttpInterceptorFn } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport {\n IDEMPOTENCY_HEADER, OFFLINE_REPLAY_HEADER, OFFLINE_SUPPORT_CONFIG, isInScope, isMutationMethod, resolveConfigCached,\n} from '../config/offline-config';\nimport { generateUuid } from '../config/uuid';\n\n/**\n * Gives every write under `apiPrefix` an `Idempotency-Key: <uuid>` unless the caller already set one (callers that must keep ONE key across\n * user retries set it themselves). The same key goes on the queued row and on every replay, so the server can recognise a repeat.\n * Left alone: reads, skipped URLs, FormData bodies, and replays (`X-Offline-Replay`, which already carry the row's key).\n */\nexport const idempotencyInterceptor: HttpInterceptorFn = (req, next) => {\n const raw = inject(OFFLINE_SUPPORT_CONFIG, { optional: true });\n if (!raw) return next(req);\n const cfg = resolveConfigCached(raw);\n if (!isMutationMethod(req.method)) return next(req);\n if (req.headers.has(OFFLINE_REPLAY_HEADER)) return next(req);\n if (!isInScope(req.url, cfg)) return next(req);\n if (typeof FormData !== 'undefined' && req.body instanceof FormData) return next(req);\n if (req.headers.has(IDEMPOTENCY_HEADER)) return next(req);\n return next(req.clone({ setHeaders: { [IDEMPOTENCY_HEADER]: generateUuid() } }));\n};\n","import { HttpErrorResponse, HttpEvent, HttpInterceptorFn, HttpResponse } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { Observable, defer, throwError, timer } from 'rxjs';\nimport { finalize, retry, tap, timeout } from 'rxjs/operators';\nimport { UiLabelsService } from '@forgeroute/ui';\nimport {\n OFFLINE_REPLAY_HEADER, OFFLINE_SUPPORT_CONFIG, isInScope, matchesAny, resolveConfigCached,\n} from '../config/offline-config';\nimport { ConnectionStateService } from '../state/connection-state.service';\nimport { RequestTimeoutError, isRequestTimeoutError } from './errors';\nimport { REQUEST_TIMEOUT_MS } from './request-timeout';\n\n/** Pause before the single automatic retry of a read that failed without an answer. */\nexport const READ_RETRY_DELAY_MS = 1000;\n\nexport function isNetworkFailure(err: unknown): boolean {\n return isRequestTimeoutError(err) || (err instanceof HttpErrorResponse && err.status === 0);\n}\n\n/**\n * Measures every API request (feeds ConnectionStateService), applies the client timeout and retries a failed READ once.\n * A write is never retried here: after a timeout or status 0 its outcome is unknown, so the user decides (the same Idempotency-Key makes\n * a retry safe). Sits closest to the network so the offline interceptor sees timeouts and failures as errors.\n */\nexport const connectionInterceptor: HttpInterceptorFn = (req, next) => {\n const raw = inject(OFFLINE_SUPPORT_CONFIG, { optional: true });\n if (!raw) return next(req);\n const cfg = resolveConfigCached(raw);\n if (!isInScope(req.url, cfg)) return next(req);\n\n const connection = inject(ConnectionStateService);\n const labels = inject(UiLabelsService);\n const method = req.method.toUpperCase();\n const isRead = method === 'GET' || method === 'HEAD' || method === 'OPTIONS';\n const long = req.responseType === 'blob' || req.responseType === 'arraybuffer' || req.reportProgress\n || (typeof FormData !== 'undefined' && req.body instanceof FormData) || matchesAny(req.url, cfg.longRequests);\n const override = req.context.get(REQUEST_TIMEOUT_MS);\n const timeoutMs = override !== null && override !== undefined ? override : long ? cfg.timeouts.long : isRead ? cfg.timeouts.read : cfg.timeouts.write;\n // The marker only exists for the interceptors in front of this one; the network does not need to hear about it.\n const outgoing = req.headers.has(OFFLINE_REPLAY_HEADER) ? req.clone({ headers: req.headers.delete(OFFLINE_REPLAY_HEADER) }) : req;\n\n const attempt = (): Observable<HttpEvent<unknown>> => defer(() => {\n const end = connection.beginRequest({ long });\n const started = Date.now();\n let outcome: { status: number; ok: boolean; timedOut: boolean } | null = null;\n let stream: Observable<HttpEvent<unknown>> = next(outgoing);\n if (timeoutMs > 0) {\n stream = stream.pipe(timeout({\n each: timeoutMs,\n with: () => throwError(() => new RequestTimeoutError(labels.t(isRead ? 'connection.timeout.read' : 'connection.timeout.write'), method, req.urlWithParams, !isRead)),\n }));\n }\n return stream.pipe(\n tap({\n next: ev => { if (ev instanceof HttpResponse) outcome = { status: ev.status, ok: ev.ok, timedOut: false }; },\n error: err => {\n if (isRequestTimeoutError(err)) outcome = { status: 0, ok: false, timedOut: true };\n else if (err instanceof HttpErrorResponse) outcome = { status: err.status, ok: false, timedOut: false };\n },\n }),\n finalize(() => {\n end();\n if (outcome) {\n connection.recordRequest({\n durationMs: Math.max(0, Date.now() - started), ok: outcome.ok, status: outcome.status, timedOut: outcome.timedOut, method, long,\n });\n }\n }),\n );\n });\n\n if (!isRead || long) return attempt();\n return attempt().pipe(retry({\n count: 1,\n delay: err => (isNetworkFailure(err) && connection.state() !== 'offline' ? timer(READ_RETRY_DELAY_MS) : throwError(() => err)),\n }));\n};\n","import { DOCUMENT } from '@angular/common';\nimport { DestroyRef, Injectable, inject, signal } from '@angular/core';\nimport { ToastService, UiLabelsService } from '@forgeroute/ui';\nimport { OFFLINE_SUPPORT_CONFIG, resolveConfig } from '../config/offline-config';\nimport { OfflineReplayer, ReplayResult, ReplayTrigger } from '../replay/offline-replayer';\nimport { ConnectionStateService } from '../state/connection-state.service';\nimport { StaleDataService } from '../state/stale-data.service';\nimport { OfflineMutation, OfflineStore } from '../store/offline-store';\n\n/** What tabs tell each other: `changed` = the saved changes changed (re-read them), `synced` = rows were delivered (refresh open pages). */\nexport type CrossTabMessage = { type: 'changed' } | { type: 'synced' };\n\n/** Creates the cross-tab channel; null when the browser has no BroadcastChannel (the focus / visibility poll still keeps tabs in step). */\nexport function createChannel(name: string): BroadcastChannel | null {\n try {\n return typeof BroadcastChannel === 'function' ? new BroadcastChannel(name) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * What the app talks to: counts, the list for the pending-changes drawer, Sync now / Retry / Discard, and the session helpers.\n * Everything is scoped to `config.getUserId()`; nothing here ever deletes saved changes unless the user chose it\n * (`discard`, `discardAll`, `endSession({ discardPending: true })` after the app asked the user).\n */\n@Injectable({ providedIn: 'root' })\nexport class OfflineSupportService {\n private readonly cfg = resolveConfig(inject(OFFLINE_SUPPORT_CONFIG, { optional: true }) ?? { getUserId: () => null });\n private readonly store = inject(OfflineStore);\n private readonly replayer = inject(OfflineReplayer);\n private readonly connection = inject(ConnectionStateService);\n private readonly stale = inject(StaleDataService);\n private readonly toast = inject(ToastService);\n private readonly labels = inject(UiLabelsService);\n private readonly doc = inject(DOCUMENT);\n private readonly destroyRef = inject(DestroyRef);\n\n private readonly _rows = signal<readonly OfflineMutation[]>([]);\n private readonly _lastQueued = signal<{ label: string; at: number } | null>(null);\n private readonly _storageAvailable = signal(true);\n\n readonly pendingCount = this.replayer.pendingCount;\n readonly failedCount = this.replayer.failedCount;\n readonly conflictCount = this.replayer.conflictCount;\n readonly syncing = this.replayer.syncing;\n readonly lastSyncAt = this.replayer.lastSyncAt;\n readonly syncTick = this.replayer.syncTick;\n readonly authBlocked = this.replayer.authBlocked;\n readonly lastResult = this.replayer.lastResult;\n /** The signed-in user's saved changes, oldest first, for the drawer. Updates when another tab changes them. */\n readonly rows = this._rows.asReadonly();\n /** The most recent change saved on this device while offline ({ label, at }). */\n readonly lastQueued = this._lastQueued.asReadonly();\n /** False when IndexedDB could not be opened on this device (set after the store initialised): the drawer then must not say \"all synced\". */\n readonly storageAvailable = this._storageAvailable.asReadonly();\n\n private started = false;\n private channel: BroadcastChannel | null = null;\n private refreshing: Promise<void> | null = null;\n private dirty = false;\n private lastKnownUserId: string | null = null;\n private cleanups: (() => void)[] = [];\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n private destroyed = false;\n\n /** Wires the triggers (called by `provideOfflineSupport`'s initializer; calling it again does nothing). Resolves when the first read is done. */\n async start(): Promise<void> {\n if (this.started) return;\n this.started = true;\n await this.store.init();\n this._storageAvailable.set(this.store.available());\n\n this.channel = createChannel(`forgeroute-offline:${this.cfg.dbName}`);\n if (this.channel) {\n const channel = this.channel;\n channel.onmessage = ev => this.onRemote(ev.data as CrossTabMessage);\n this.cleanups.push(() => { channel.onmessage = null; channel.close(); });\n }\n const win = this.doc.defaultView;\n if (win) {\n const poll = () => { if (this.doc.visibilityState !== 'hidden') void this.refresh(); };\n win.addEventListener('focus', poll);\n this.doc.addEventListener('visibilitychange', poll);\n this.cleanups.push(() => { win.removeEventListener('focus', poll); this.doc.removeEventListener('visibilitychange', poll); });\n }\n const subs = [\n this.connection.recovered$.subscribe(trigger => this.replay(trigger)),\n this.store.mutated$.subscribe(() => { void this.refresh(); this.post({ type: 'changed' }); }),\n this.replayer.runCompleted$.subscribe(({ changed }) => { if (changed) this.post({ type: 'synced' }); }),\n // A run that ended with rows still waiting out a backoff must schedule their next try (nothing else would).\n this.replayer.runCompleted$.subscribe(() => { void this.refresh(); }),\n ];\n this.cleanups.push(() => subs.forEach(s => s.unsubscribe()));\n this.destroyRef.onDestroy(() => {\n this.destroyed = true;\n this.clearRetryTimer();\n this.cleanups.forEach(c => c()); this.cleanups = []; this.channel = null;\n });\n\n await this.refresh();\n if (this.connection.isOnline() && this.cfg.getUserId()) this.replay('start');\n }\n\n /** Starts a replay run without ever leaving an unhandled rejection behind. */\n private replay(trigger: ReplayTrigger): void {\n void this.replayer.replayAll(trigger).catch(() => undefined);\n }\n\n private clearRetryTimer(): void {\n if (this.retryTimer) { clearTimeout(this.retryTimer); this.retryTimer = null; }\n }\n\n /**\n * Rows that failed with a transient error wait for `nextAttemptAt`. Nothing else triggers a run while the connection stays `online`, so a timer\n * fires one replay when the earliest of them is due.\n */\n private scheduleRetry(): void {\n this.clearRetryTimer();\n if (this.destroyed) return;\n let earliest: number | null = null;\n for (const r of this._rows()) {\n if (r.status === 'pending' && r.nextAttemptAt !== undefined && (earliest === null || r.nextAttemptAt < earliest)) earliest = r.nextAttemptAt;\n }\n if (earliest === null) return;\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n // While offline (or signed out) nothing is sent; the recovery trigger replays everything that is due once the connection is back.\n if (this.cfg.getUserId() && this.connection.isOnline()) this.replay('online');\n }, Math.max(0, earliest - Date.now()) + 50);\n }\n\n private post(msg: CrossTabMessage): void {\n try { this.channel?.postMessage(msg); } catch { /* channel closed */ }\n }\n\n private onRemote(msg: CrossTabMessage | null): void {\n if (!msg) return;\n if (msg.type === 'synced') this.replayer.noteRemoteSync();\n void this.refresh();\n }\n\n /** Called by the offline interceptor after a write was saved on the device. */\n onQueued(row: OfflineMutation): void {\n this.lastKnownUserId = row.userId;\n this._lastQueued.set({ label: row.label, at: Date.now() });\n this.toast.info(this.labels.t('connection.savedOnDevice'));\n void this.refresh();\n }\n\n /** Re-reads the rows and counts. Calls that overlap are merged into one extra read. */\n refresh(): Promise<void> {\n if (this.refreshing) { this.dirty = true; return this.refreshing; }\n const p = this.doRefresh().finally(() => { this.refreshing = null; });\n this.refreshing = p;\n return p;\n }\n\n private async doRefresh(): Promise<void> {\n do {\n this.dirty = false;\n const userId = this.cfg.getUserId();\n if (!userId) {\n this._rows.set([]);\n } else {\n this.lastKnownUserId = userId;\n await this.store.init();\n const rows = await this.store.list(userId);\n if (this.cfg.getUserId() !== userId) { this.dirty = true; continue; }\n this._rows.set(rows);\n }\n await this.replayer.refreshCounts();\n } while (this.dirty);\n this.scheduleRetry();\n }\n\n /** \"Sync now\": sends everything due, even rows waiting out a backoff. */\n async syncNow(): Promise<ReplayResult | null> {\n const result = await this.replayer.replayAll('manual');\n await this.refresh();\n return result;\n }\n\n async retry(id: string): Promise<void> {\n await this.replayer.retryOne(id);\n await this.refresh();\n }\n\n async discard(id: string): Promise<void> {\n await this.replayer.discard(id);\n await this.refresh();\n }\n\n async discardAll(): Promise<void> {\n await this.replayer.discardAll();\n await this.refresh();\n }\n\n // ---- session helpers -----------------------------------------------------------------------------------------------------------\n\n /**\n * Call in logout BEFORE signing out. `pending` = changes not yet delivered (waiting or being sent), `failed` = changes that need attention\n * (failed or conflict). When `pending + failed > 0` show a ConfirmService dialog with `connection.logoutPending.one` / `connection.logoutPending.other`; then call\n * `endSession({ discardPending })` with the user's choice. Nothing is deleted here.\n */\n async beforeSessionEnd(): Promise<{ pending: number; failed: number }> {\n const userId = this.cfg.getUserId() ?? this.lastKnownUserId;\n if (!userId) return { pending: 0, failed: 0 };\n await this.store.init();\n if (!this.store.available()) return { pending: 0, failed: 0 };\n const c = await this.store.countsByStatus(userId);\n return { pending: c.pending + c.syncing, failed: c.failed + c.conflict };\n }\n\n /** The signed-in user changed (sign-in, switch): re-scope the view to them and send what they have waiting. */\n async onSessionStart(userId: string): Promise<void> {\n this.lastKnownUserId = userId;\n this.replayer.resetView();\n this._lastQueued.set(null);\n this.stale.clear();\n await this.refresh();\n if (this.cfg.getUserId() === userId) this.replay('start');\n }\n\n /**\n * Called by the offline interceptor for every in-scope request: when the signed-in user is not the one the view was built for (the app never\n * called `onSessionStart`), forget the previous user's rows and counts and re-read for the new one.\n */\n ensureScope(): void {\n const current = this.cfg.getUserId();\n if (current === this.lastKnownUserId) return;\n if (this.lastKnownUserId === null) {\n // First sight of a user: nothing from a previous user can be on screen yet, so only read their rows.\n this.lastKnownUserId = current;\n void this.refresh();\n return;\n }\n this.replayer.resetView();\n this._rows.set([]);\n this._lastQueued.set(null);\n this.stale.clear();\n this.lastKnownUserId = current;\n void this.refresh();\n }\n\n /**\n * The user signed out. By default NOTHING is deleted: the saved changes and the device copy stay for that user and are sent when they sign in again.\n * Only `discardPending: true` (the app asked the user first) deletes that one user's changes and device copy. Other users' data is never touched.\n * `userId` names the user who is leaving; pass it when the app already switched to somebody else (impersonation, portal logout), otherwise the\n * user the view was last built for is used.\n */\n async endSession(options: { discardPending: boolean; userId?: string }): Promise<void> {\n const userId = options.userId ?? this.lastKnownUserId ?? this.cfg.getUserId();\n if (options.discardPending && userId) await this.store.clearUser(userId);\n this._rows.set([]);\n this._lastQueued.set(null);\n this.replayer.resetView();\n this.stale.clear();\n }\n\n /**\n * Empties the device copy of every user's GET answers and the in-memory view. Saved changes are kept unless `discardPending` is true, which wipes\n * them for EVERY user: only for a deliberate full reset the user confirmed, never at logout.\n */\n async clearAll(options: { discardPending?: boolean } = {}): Promise<void> {\n if (options.discardPending) await this.store.clearAll();\n else await this.store.clearCache();\n this._rows.set([]);\n this._lastQueued.set(null);\n this.replayer.resetView();\n this.stale.clear();\n }\n}\n","import { HttpErrorResponse, HttpEvent, HttpHeaders, HttpInterceptorFn, HttpParams, HttpRequest, HttpResponse } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { Observable, from, of, throwError } from 'rxjs';\nimport { catchError, switchMap, tap } from 'rxjs/operators';\nimport { UiLabelsService, uiText } from '@forgeroute/ui';\nimport {\n DEFAULT_DETAIL_TTL_MS, DEFAULT_LIST_TTL_MS, FROM_CACHE_HEADER, IDEMPOTENCY_HEADER, OFFLINE_REPLAY_HEADER, OFFLINE_SUPPORT_CONFIG,\n QueueRule, ResolvedOfflineConfig, deriveEntity, invalidationPrefix, isInScope, isMutationMethod, lastSegmentIsId, matchesAny, matchesUrl,\n pathAndQuery, pathOnly, resolveConfigCached,\n} from '../config/offline-config';\nimport { generateUuid } from '../config/uuid';\nimport { ConnectionStateService } from '../state/connection-state.service';\nimport { StaleDataService } from '../state/stale-data.service';\nimport { OfflineMutation, OfflineStore } from '../store/offline-store';\nimport { findTempIds, isTempIdCreate } from '../store/temp-ids';\nimport { OfflineSupportService } from '../support/offline-support.service';\nimport { OfflineBlockedError } from './errors';\nimport { isNetworkFailure } from './connection.interceptor';\n\nconst FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded;charset=UTF-8';\n\n/** Header names that must never be copied onto a queued row, whatever `replayHeaders` says: the live token is attached at replay time. */\nconst NEVER_STORED_HEADERS = ['authorization', 'cookie', 'proxy-authorization'];\n\nfunction isUploadBody(body: unknown): boolean {\n return (typeof FormData !== 'undefined' && body instanceof FormData)\n || (typeof Blob !== 'undefined' && body instanceof Blob)\n || body instanceof ArrayBuffer\n || ArrayBuffer.isView(body);\n}\n\nfunction isFormBody(body: unknown): body is HttpParams | URLSearchParams {\n return body instanceof HttpParams || (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams);\n}\n\n/** A structured-clone-safe copy of the JSON body (undefined values dropped), or `undefined` when it cannot be stored. */\nfunction storableBody(body: unknown): { ok: true; value: unknown } | { ok: false } {\n if (body === null || body === undefined) return { ok: true, value: null };\n if (isFormBody(body)) return { ok: true, value: body.toString() };\n if (typeof body === 'string' || typeof body === 'number' || typeof body === 'boolean') return { ok: true, value: body };\n try {\n return { ok: true, value: JSON.parse(JSON.stringify(body)) };\n } catch {\n return { ok: false };\n }\n}\n\n/**\n * The label a queued change gets when no rule writes one: \"Add lots\" / \"Update lots\" / \"Delete lots\" (the entity is the URL's own word, \"item\" when the\n * URL has none). It is written in the language active when the change is queued and saved on the row as such - a row stored before a language\n * switch keeps its old wording.\n */\nfunction defaultLabel(labels: UiLabelsService, method: string, url: string): string {\n const key = method === 'POST' ? 'connection.defaultLabel.add' : method === 'DELETE' ? 'connection.defaultLabel.delete' : 'connection.defaultLabel.update';\n const segments = pathOnly(url).split('/').filter(Boolean);\n const entity = deriveEntity(url).entityType ?? segments[segments.length - 1] ?? labels.t('connection.entityFallback');\n return labels.t(key, { entity });\n}\n\nfunction findRule(rules: readonly QueueRule[], req: HttpRequest<unknown>): QueueRule | undefined {\n const target = pathAndQuery(req.urlWithParams);\n const method = req.method.toUpperCase();\n return rules.find(r => {\n r.pattern.lastIndex = 0;\n const hit = r.pattern.test(target);\n r.pattern.lastIndex = 0;\n return hit && r.methods.some(m => m.toUpperCase() === method);\n });\n}\n\nfunction safeHeaders(req: HttpRequest<unknown>, cfg: ResolvedOfflineConfig, formBody: boolean): Record<string, string> | undefined {\n const out: Record<string, string> = {};\n const contentType = req.headers.get('Content-Type');\n if (contentType && !/^application\\/json/i.test(contentType)) out['Content-Type'] = contentType;\n else if (formBody) out['Content-Type'] = FORM_CONTENT_TYPE;\n for (const name of cfg.replayHeaders) {\n if (NEVER_STORED_HEADERS.includes(name.toLowerCase())) continue;\n const value = req.headers.get(name);\n if (value !== null) out[name] = value;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * Reads: network first; the answer is also stored (per user) unless excluded; while offline/unreachable, or when the request fails with no\n * answer, the stored copy is served (header `X-From-Offline-Cache: <cachedAt>`, StaleDataService told) and otherwise the error is rethrown.\n * Writes while offline/unreachable: blocked (OfflineBlockedError) or saved on the device with the request's Idempotency-Key and answered with a\n * typed synthetic 202. Writes while online pass through; a write with an unknown outcome (timeout / status 0 while the browser is online) is\n * never queued automatically.\n */\nexport const offlineInterceptor: HttpInterceptorFn = (req, next) => {\n const raw = inject(OFFLINE_SUPPORT_CONFIG, { optional: true });\n if (!raw) return next(req);\n const cfg = resolveConfigCached(raw);\n if (req.headers.has(OFFLINE_REPLAY_HEADER) || !isInScope(req.url, cfg)) return next(req);\n // A changed signed-in user must not keep showing the previous user's saved changes: re-scope the view (cheap no-op when unchanged).\n inject(OfflineSupportService).ensureScope();\n if (req.method === 'GET') return handleRead(req, next, cfg);\n if (isMutationMethod(req.method)) return handleWrite(req, next, cfg);\n return next(req);\n};\n\nfunction cachedResponse(req: HttpRequest<unknown>, data: unknown, cachedAt: number): HttpResponse<unknown> {\n return new HttpResponse({\n body: data, status: 200, statusText: 'OK', url: req.urlWithParams,\n headers: new HttpHeaders({ [FROM_CACHE_HEADER]: String(cachedAt) }),\n });\n}\n\nfunction handleRead(req: HttpRequest<unknown>, next: (r: HttpRequest<unknown>) => Observable<HttpEvent<unknown>>, cfg: ResolvedOfflineConfig): Observable<HttpEvent<unknown>> {\n const userId = cfg.getUserId();\n if (userId === null || req.responseType !== 'json' || matchesAny(req.url, cfg.neverCache)) return next(req);\n\n const connection = inject(ConnectionStateService);\n const store = inject(OfflineStore);\n const stale = inject(StaleDataService);\n const key = pathAndQuery(req.urlWithParams);\n const ttl = cfg.cacheTtl ? cfg.cacheTtl(req.url) : lastSegmentIsId(req.url) ? DEFAULT_DETAIL_TTL_MS : DEFAULT_LIST_TTL_MS;\n\n const live = (): Observable<HttpEvent<unknown>> => next(req).pipe(tap(ev => {\n if (!(ev instanceof HttpResponse) || ev.status !== 200) return;\n stale.clearKey(key);\n const noStore = (ev.headers.get('Cache-Control') ?? '').toLowerCase().includes('no-store');\n if (!noStore && ev.body !== null && ev.body !== undefined) void store.cacheResponse(key, userId, ev.body, ttl).catch(() => undefined);\n }));\n\n const serveCopyOr = (otherwise: () => Observable<HttpEvent<unknown>>): Observable<HttpEvent<unknown>> =>\n from(store.getCached(key, userId)).pipe(switchMap(entry => {\n if (!entry) return otherwise();\n stale.markStale(entry.cachedAt, key);\n return of(cachedResponse(req, entry.data, entry.cachedAt));\n }));\n\n if (connection.isOffline()) {\n return serveCopyOr(() => connection.state() === 'unreachable'\n ? live() // nothing saved: try the network anyway, it may be back\n : throwError(() => new HttpErrorResponse({ url: req.urlWithParams, status: 0, statusText: 'Offline', error: uiText('connection.noCopy') })));\n }\n return live().pipe(catchError(err => isNetworkFailure(err) ? serveCopyOr(() => throwError(() => err)) : throwError(() => err)));\n}\n\nfunction handleWrite(req: HttpRequest<unknown>, next: (r: HttpRequest<unknown>) => Observable<HttpEvent<unknown>>, cfg: ResolvedOfflineConfig): Observable<HttpEvent<unknown>> {\n const connection = inject(ConnectionStateService);\n const userId = cfg.getUserId();\n\n if (!connection.isOffline() || userId === null) {\n if (userId === null) return next(req);\n const store = inject(OfflineStore);\n return next(req).pipe(tap(ev => {\n if (ev instanceof HttpResponse && ev.ok) void store.invalidatePrefix(invalidationPrefix(req.url), userId).catch(() => undefined);\n }));\n }\n\n const labels = inject(UiLabelsService);\n const block = cfg.blockOffline.find(b => matchesUrl(req.urlWithParams, b.pattern));\n if (block) {\n return throwError(() => new OfflineBlockedError(block.message ?? labels.t('connection.blocked', { action: labels.t('connection.thisAction') }), 'blocked'));\n }\n if (isUploadBody(req.body)) {\n return throwError(() => new OfflineBlockedError(labels.t('connection.uploadBlocked'), 'upload'));\n }\n let rule: QueueRule | undefined;\n if (cfg.allowQueue) {\n rule = findRule(cfg.allowQueue, req);\n if (!rule) return throwError(() => new OfflineBlockedError(labels.t('connection.blocked', { action: labels.t('connection.thisAction') }), 'not-allowed'));\n }\n const body = storableBody(req.body);\n if (!body.ok) return throwError(() => new OfflineBlockedError(labels.t('connection.storageUnavailable'), 'storage'));\n\n const store = inject(OfflineStore);\n const support = inject(OfflineSupportService);\n const method = req.method.toUpperCase();\n const url = req.urlWithParams;\n const derived = deriveEntity(url);\n const row = {\n id: generateUuid(),\n userId,\n key: req.headers.get(IDEMPOTENCY_HEADER) ?? generateUuid(),\n method,\n url,\n body: body.value,\n headers: safeHeaders(req, cfg, isFormBody(req.body)),\n label: rule?.label?.(method, url, req.body) ?? defaultLabel(labels, method, url),\n entityType: rule?.entityType?.(url) ?? derived.entityType,\n entityId: derived.entityId,\n createdAt: Date.now(),\n status: 'pending' as const,\n attempts: 0,\n };\n // A create gets its own id as the temp id; a change that refers to the temp id of a create that is still queued remembers that create.\n const tempId = isTempIdCreate(method, url, body.value) ? row.id : undefined;\n return from(store.list(userId).catch((): OfflineMutation[] => [])).pipe(\n switchMap(existing => {\n const creates = new Map<string, string>();\n for (const r of existing) if (r.tempId) creates.set(r.tempId, r.id);\n const refs = findTempIds(url, body.value, creates);\n return store.addMutation({\n ...row,\n ...(tempId ? { tempId } : {}),\n ...(refs.length > 0 ? { dependsOn: refs.map(t => creates.get(t) as string), dependsOnTempIds: refs } : {}),\n });\n }),\n catchError(() => throwError(() => new OfflineBlockedError(labels.t('connection.storageUnavailable'), 'storage'))),\n switchMap(saved => {\n // The same key already belongs to a row that will never be sent again (failed / conflict): answering \"saved\" would be a fake success.\n if (saved.status === 'failed' || saved.status === 'conflict') {\n return throwError(() => new OfflineBlockedError(labels.t('connection.blocked', { action: labels.t('connection.thisAction') }), 'not-allowed'));\n }\n support.onQueued(saved);\n return of(saved);\n }),\n switchMap(saved => {\n const base = body.value !== null && typeof body.value === 'object' && !Array.isArray(body.value) ? body.value as Record<string, unknown> : {};\n return of(new HttpResponse<unknown>({\n status: 202, statusText: 'Accepted', url,\n body: { ...base, id: base['id'] ?? saved.tempId ?? saved.id, _offline: true },\n }));\n }),\n );\n}\n","import { HttpInterceptorFn } from '@angular/common/http';\nimport { connectionInterceptor } from './connection.interceptor';\nimport { idempotencyInterceptor } from './idempotency.interceptor';\nimport { offlineInterceptor } from './offline.interceptor';\n\n/**\n * Register LAST in `withInterceptors([... ,...offlineHttpInterceptors])` (after errorContext and the app's own auth interceptor; replays carry\n * `X-Offline-Replay`, so an app whose auth interceptor sits after these still gets the live token on replays).\n *\n * Array order is outermost first: idempotency (adds the key) -> offline (queue / device copy) -> connection (timeout, timing, GET retry, closest\n * to the network). The offline interceptor must be OUTSIDE the connection one: it has to see timeouts and status-0 failures as errors to fall back\n * to the device copy, and answers it makes itself (queued 202, device copy) must not be measured as network successes.\n */\nexport const offlineHttpInterceptors: readonly HttpInterceptorFn[] = [idempotencyInterceptor, offlineInterceptor, connectionInterceptor];\n","import { EnvironmentProviders, inject, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';\nimport { OFFLINE_SUPPORT_CONFIG, OfflineSupportConfig } from '../config/offline-config';\nimport { ConnectionStateService } from '../state/connection-state.service';\nimport { OfflineStore } from '../store/offline-store';\nimport { OfflineSupportService } from './offline-support.service';\n\n/**\n * Provides the config and starts the offline layer with the app: opens the store, then wires the replay triggers (app start when online, browser\n * `online`, connection back to `online`, probe success) and the cross-tab sync. Pair it with\n * `provideHttpClient(withInterceptors([..., ...offlineHttpInterceptors]))`.\n */\nexport function provideOfflineSupport(config: OfflineSupportConfig): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: OFFLINE_SUPPORT_CONFIG, useValue: config },\n provideAppInitializer(() => {\n const store = inject(OfflineStore);\n const support = inject(OfflineSupportService);\n inject(ConnectionStateService);\n return store.init().then(() => { void support.start().catch(() => undefined); });\n }),\n ]);\n}\n","import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, ElementRef, computed, effect, inject, input, output, signal, untracked, viewChild } from '@angular/core';\nimport { UiLabelsService } from '@forgeroute/ui';\nimport { ConnectionStateService, OfflineSupportService } from '../deps';\n\n/** How long the green \"All changes synced\" bar stays after a sync. */\nexport const SYNCED_FLASH_MS = 3000;\n\nexport type ConnectionBannerKind = 'auth' | 'offline' | 'unreachable' | 'syncing' | 'attention' | 'slow' | 'synced';\n\ninterface BannerView {\n kind: ConnectionBannerKind;\n title: string;\n message: string;\n detail: string;\n /** Spoken text: exactly what the sighted user reads. */\n announcement: string;\n}\n\nlet nextId = 0;\n\n/**\n * THE connection banner. Put ONE near the root of the app, above the routed content (inside the scrolling page, not fixed):\n *\n * <ui-connection-banner (viewChanges)=\"drawerOpen.set(true)\" />\n *\n * One plain-words message per situation, by priority: sign-in needed for saved changes > offline > server unreachable >\n * sending saved changes > changes need attention > slow connection > \"All changes synced\" (3 s, also after automatic syncs) > nothing.\n *\n * Accessibility: two live regions (polite status, assertive alert for the sign-in message) exist from the first render and only their text\n * changes, so screen readers announce every change; the visible bar is ordinary content that mirrors the same words. The bar is\n * `position: sticky; top: 0` inside the page flow, so it never covers content, and sits below modals / side panels.\n *\n * Test ids: connection-banner, connection-banner-live, connection-banner-live-alert, connection-banner-title, connection-banner-message,\n * connection-banner-detail, connection-banner-sync-now, connection-banner-view, connection-banner-info-toggle, connection-banner-info-panel,\n * connection-banner-info-hide.\n */\n@Component({\n selector: 'ui-connection-banner',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"cb-live\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" data-testid=\"connection-banner-live\">{{ politeText() }}</div>\n <div class=\"cb-live\" role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\" data-testid=\"connection-banner-live-alert\">{{ alertText() }}</div>\n @if (view(); as v) {\n <div class=\"cb-bar\" [class]=\"'cb-bar cb-' + v.kind\" role=\"group\" [attr.aria-label]=\"labels.t('connection.region')\"\n data-testid=\"connection-banner\" [attr.data-state]=\"v.kind\" (keydown.escape)=\"onEscape($event)\">\n <div class=\"cb-text\">\n <strong class=\"cb-title\" data-testid=\"connection-banner-title\">{{ v.title }}</strong>\n @if (v.message) { <span class=\"cb-msg\" data-testid=\"connection-banner-message\"> {{ v.message }}</span> }\n @if (v.detail) { <span class=\"cb-detail\" data-testid=\"connection-banner-detail\"> {{ v.detail }}</span> }\n </div>\n @if (showSyncNow() || showView() || infoAllowed()) {\n <div class=\"cb-actions\">\n @if (showSyncNow()) {\n <button type=\"button\" class=\"cb-btn\" data-testid=\"connection-banner-sync-now\" (click)=\"syncNow()\">{{ labels.t('connection.syncNow') }}</button>\n }\n @if (showView()) {\n <button type=\"button\" class=\"cb-btn\" data-testid=\"connection-banner-view\" (click)=\"viewChanges.emit()\">{{ labels.t('connection.viewChanges') }}</button>\n }\n @if (infoAllowed()) {\n <button #infoToggle type=\"button\" class=\"cb-btn cb-btn-ghost\" data-testid=\"connection-banner-info-toggle\"\n [attr.aria-expanded]=\"infoOpen()\" [attr.aria-controls]=\"panelId\" (click)=\"toggleInfo()\">{{ labels.t('connection.whatCanIDo') }}</button>\n }\n </div>\n }\n @if (infoAllowed() && infoOpen()) {\n <div class=\"cb-info\" [id]=\"panelId\" role=\"region\" tabindex=\"-1\" [attr.aria-label]=\"labels.t('connection.info.panelLabel')\" data-testid=\"connection-banner-info-panel\">\n <p class=\"cb-info-h\">{{ labels.t('connection.info.canDoTitle') }}</p>\n <ul class=\"cb-list\">@for (item of canDoItems(); track $index) { <li>{{ item }}</li> }</ul>\n <p class=\"cb-info-h\">{{ labels.t('connection.info.needsTitle') }}</p>\n <ul class=\"cb-list\">@for (item of needsItems(); track $index) { <li>{{ item }}</li> }</ul>\n <button #infoHide type=\"button\" class=\"cb-btn\" data-testid=\"connection-banner-info-hide\" (click)=\"closeInfo(true)\">{{ labels.t('connection.hide') }}</button>\n </div>\n }\n </div>\n }\n `,\n styles: [`\n :host { display: block; position: -webkit-sticky; position: sticky; top: 0; z-index: 50; }\n .cb-live { position: absolute; inline-size: 1px; block-size: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }\n .cb-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; padding: 10px 16px; font-size: 0.875rem; line-height: 1.4;\n border-block-end: 1px solid; border-inline-start: 6px solid; box-sizing: border-box; }\n .cb-text { flex: 1 1 280px; min-inline-size: 0; overflow-wrap: anywhere; }\n .cb-actions { display: flex; flex-wrap: wrap; gap: 8px; }\n .cb-btn { min-block-size: 44px; min-inline-size: 44px; padding: 0 14px; border-radius: 8px; border: 1px solid currentColor; background: #fff; color: inherit;\n font: inherit; font-weight: 600; cursor: pointer; -webkit-appearance: none; appearance: none; }\n .cb-btn-ghost { background: transparent; }\n .cb-btn:focus { outline: 3px solid #1d4ed8; outline-offset: 2px; }\n .cb-btn:focus:not(:focus-visible) { outline: none; }\n .cb-info { flex: 1 1 100%; padding: 12px 14px; border-radius: 8px; background: rgba(255, 255, 255, 0.7); border: 1px solid currentColor; }\n .cb-info:focus { outline: none; }\n .cb-info-h { margin: 0 0 4px; font-weight: 700; }\n .cb-list { margin: 0 0 12px; padding-inline-start: 20px; }\n .cb-offline, .cb-slow, .cb-attention { background: #fef3c7; color: #78350f; border-color: #b45309; }\n .cb-unreachable, .cb-auth { background: #fee2e2; color: #7f1d1d; border-color: #b91c1c; }\n .cb-syncing { background: #dbeafe; color: #1e3a8a; border-color: #1d4ed8; }\n .cb-synced { background: #dcfce7; color: #14532d; border-color: #15803d; }\n @media (prefers-reduced-motion: no-preference) { .cb-bar { animation: cb-in 160ms ease-out; } }\n @keyframes cb-in { from { opacity: 0; } to { opacity: 1; } }\n @media (max-width: 480px) {\n .cb-actions { flex-direction: column; inline-size: 100%; }\n .cb-btn { inline-size: 100%; }\n }\n @media (forced-colors: active) {\n .cb-bar, .cb-info { border: 2px solid CanvasText; background: Canvas; color: CanvasText; }\n .cb-btn { border-color: ButtonText; background: ButtonFace; color: ButtonText; }\n }\n `],\n})\nexport class ConnectionBannerComponent {\n protected readonly labels = inject(UiLabelsService);\n private readonly connection = inject(ConnectionStateService);\n private readonly support = inject(OfflineSupportService);\n private readonly cdr = inject(ChangeDetectorRef);\n\n /** What the user can still do while offline (app specific). Default: a generic list. */\n readonly infoOffline = input<readonly string[] | null>(null);\n /** What needs a connection (app specific, should match what the app really blocks). Default: a generic list. */\n readonly infoNeedsConnection = input<readonly string[] | null>(null);\n /** The user pressed \"View changes\" - the app opens `<ui-pending-changes-drawer>`. */\n readonly viewChanges = output<void>();\n\n protected readonly panelId = `ui-cb-info-${++nextId}`;\n protected readonly infoOpen = signal(false);\n private readonly infoToggleRef = viewChild<ElementRef<HTMLButtonElement>>('infoToggle');\n private readonly infoHideRef = viewChild<ElementRef<HTMLButtonElement>>('infoHide');\n\n private readonly syncTotal = signal(0);\n private readonly showSynced = signal(false);\n private syncedTimer: ReturnType<typeof setTimeout> | null = null;\n private seenTick = this.support.syncTick();\n private flashPending = false;\n private wasSyncing = false;\n\n protected readonly canDoItems = computed(() => this.infoOffline() ?? [this.labels.t('connection.info.canDo1'), this.labels.t('connection.info.canDo2')]);\n protected readonly needsItems = computed(() => this.infoNeedsConnection() ?? [this.labels.t('connection.info.needs1')]);\n\n private count(base: 'connection.syncing' | 'connection.needAttention' | 'connection.pending', n: number): string {\n return this.labels.t(`${base}.${n === 1 ? 'one' : 'other'}`, { count: n });\n }\n\n protected readonly view = computed((): BannerView | null => {\n const state = this.connection.state();\n const pending = this.support.pendingCount();\n const attention = this.support.failedCount() + this.support.conflictCount();\n const l = this.labels;\n let v: Omit<BannerView, 'announcement'> | null = null;\n if (this.support.authBlocked()) {\n v = { kind: 'auth', title: l.t('connection.authBlocked'), message: '', detail: '' };\n } else if (state === 'offline') {\n v = { kind: 'offline', title: l.t('connection.offline.title'), message: l.t('connection.offline.message'), detail: pending > 0 ? this.count('connection.pending', pending) : '' };\n } else if (state === 'unreachable') {\n v = { kind: 'unreachable', title: l.t('connection.unreachable.title'), message: l.t('connection.unreachable.message'), detail: pending > 0 ? this.count('connection.pending', pending) : '' };\n } else if (this.support.syncing() && Math.max(this.syncTotal(), pending) > 0) {\n v = { kind: 'syncing', title: this.count('connection.syncing', Math.max(this.syncTotal(), pending)), message: '', detail: '' };\n } else if (attention > 0) {\n v = { kind: 'attention', title: this.count('connection.needAttention', attention), message: '', detail: '' };\n } else if (state === 'slow') {\n v = { kind: 'slow', title: l.t('connection.slow.title'), message: l.t('connection.slow.message'), detail: '' };\n } else if (this.showSynced()) {\n v = { kind: 'synced', title: l.t('connection.synced'), message: '', detail: '' };\n }\n if (!v) return null;\n const sentence = (s: string) => (/[.…!?]$/.test(s) ? s : s + '.');\n return { ...v, announcement: [v.title, v.message, v.detail].filter(Boolean).map(sentence).join(' ') };\n });\n\n /** Set when a \"Sync now\" call was rejected: said politely in the status region (nothing is lost, the changes stay saved). */\n private readonly actionFailure = signal('');\n protected readonly politeText = computed(() => {\n const v = this.view();\n return [v && v.kind !== 'auth' ? v.announcement : '', this.actionFailure()].filter(Boolean).join(' ');\n });\n protected readonly alertText = computed(() => { const v = this.view(); return v && v.kind === 'auth' ? v.announcement : ''; });\n\n protected readonly infoAllowed = computed(() => { const k = this.view()?.kind; return k === 'offline' || k === 'unreachable'; });\n protected readonly showSyncNow = computed(() => {\n const k = this.view()?.kind;\n return (k === 'unreachable' || k === 'slow' || k === 'attention') && this.support.pendingCount() > 0 && !this.support.syncing();\n });\n protected readonly showView = computed(() => {\n const k = this.view()?.kind;\n if (!k || k === 'synced') return false;\n return this.rowsTotal() > 0 || k === 'auth';\n });\n private readonly rowsTotal = computed(() => this.support.pendingCount() + this.support.failedCount() + this.support.conflictCount());\n\n constructor() {\n // Remember how many changes a sync started with (\"Sending 3 changes…\" must not count down to 0 while it runs), and flash\n // \"All changes synced\" when a run that changed data (syncTick bumped) ends - manual and automatic runs alike.\n effect(() => {\n const syncing = this.support.syncing();\n const tick = this.support.syncTick();\n untracked(() => {\n if (tick !== this.seenTick) { this.seenTick = tick; this.flashPending = true; }\n if (syncing && !this.wasSyncing) this.syncTotal.set(Math.max(this.support.pendingCount(), 0));\n if (!syncing) this.syncTotal.set(0);\n this.wasSyncing = syncing;\n if (!syncing && this.flashPending) { this.flashPending = false; this.flashSynced(); }\n });\n });\n effect(() => { if (!this.infoAllowed()) this.infoOpen.set(false); });\n inject(DestroyRef).onDestroy(() => { if (this.syncedTimer) clearTimeout(this.syncedTimer); });\n }\n\n private flashSynced(): void {\n if (this.syncedTimer) clearTimeout(this.syncedTimer);\n this.showSynced.set(true);\n this.syncedTimer = setTimeout(() => { this.syncedTimer = null; this.showSynced.set(false); }, SYNCED_FLASH_MS);\n }\n\n protected syncNow(): void {\n this.actionFailure.set('');\n let call: Promise<unknown>;\n try { call = Promise.resolve(this.support.syncNow()); } catch { call = Promise.reject(new Error('syncNow')); }\n call.catch(() => this.actionFailure.set(this.labels.t('connection.error.unknown')));\n }\n\n protected toggleInfo(): void {\n if (this.infoOpen()) { this.closeInfo(true); return; }\n this.infoOpen.set(true);\n this.cdr.detectChanges();\n this.infoHideRef()?.nativeElement.focus();\n }\n\n protected closeInfo(returnFocus: boolean): void {\n this.infoOpen.set(false);\n this.cdr.detectChanges();\n if (returnFocus) this.infoToggleRef()?.nativeElement.focus();\n }\n\n protected onEscape(event: Event): void {\n if (!this.infoOpen()) return;\n event.stopPropagation();\n this.closeInfo(true);\n }\n}\n","/**\n * \"5 minutes ago\" for the offline UI. Uses `Intl.RelativeTimeFormat` when the browser has it (feature-detected; every current\n * Chrome / Edge / Firefox / Safari does) and falls back to the browser's own date formatting otherwise. `locale` undefined = the\n * page language / browser default.\n */\nexport function formatRelativeTime(then: number, now: number, justNowText: string, locale?: string): string {\n const diffSec = Math.round((now - then) / 1000);\n if (diffSec < 45) return justNowText;\n const rtfCtor = (globalThis as { Intl?: { RelativeTimeFormat?: new (l?: string, o?: { numeric: 'auto' | 'always' }) => { format(v: number, u: string): string } } }).Intl?.RelativeTimeFormat;\n const minutes = Math.round(diffSec / 60);\n const hours = Math.round(diffSec / 3600);\n const days = Math.round(diffSec / 86400);\n if (rtfCtor) {\n let rtf: { format(v: number, u: string): string };\n try { rtf = new rtfCtor(locale, { numeric: 'auto' }); } catch { rtf = new rtfCtor(undefined, { numeric: 'auto' }); }\n if (minutes < 60) return rtf.format(-minutes, 'minute');\n if (hours < 24) return rtf.format(-hours, 'hour');\n if (days <= 30) return rtf.format(-days, 'day');\n }\n return absoluteTime(then, locale);\n}\n\n/** Full local date and time, e.g. for a `title` / `<time>` tooltip. */\nexport function absoluteTime(at: number, locale?: string): string {\n try { return new Date(at).toLocaleString(locale); } catch { return new Date(at).toLocaleString(); }\n}\n\n/**\n * The language of the times the offline UI shows. With `labelsLocale` (the label service's `locale()`, which follows the active dictionary's\n * `i18n.locale` marker or the app's `setLocale`) it always wins, so the times agree with the texts around them. Without `labelsLocale` it is the\n * page language (`<html lang>`) only (`undefined` = the browser default).\n */\nexport function pageLocale(doc: Document, labelsLocale?: string): string | undefined {\n if (labelsLocale) return labelsLocale;\n return doc.documentElement?.lang || undefined;\n}\n","import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, ElementRef, computed, inject, input, output, signal } from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\nimport { ConfirmService, SidePanelComponent, UiLabelsService } from '@forgeroute/ui';\nimport { OfflineSupportService, PendingRow } from '../deps';\nimport { absoluteTime, formatRelativeTime, pageLocale } from '../relative-time';\n\ntype ErrorKind = 'conflict' | 'rejected' | 'auth' | 'server' | 'network' | 'timeout' | 'dependency';\nconst ERROR_KEYS = {\n conflict: 'connection.error.conflict',\n rejected: 'connection.error.rejected',\n auth: 'connection.error.auth',\n server: 'connection.error.server',\n network: 'connection.error.network',\n timeout: 'connection.error.timeout',\n dependency: 'connection.error.dependency',\n} as const;\n\n/**\n * Side panel that lists every change saved on this device and not yet accepted by the server, with plain-words reasons and\n * the actions Retry / Discard per change, Sync all and Discard all. Open it from `(viewChanges)` of `<ui-connection-banner>`:\n *\n * <ui-pending-changes-drawer [open]=\"drawerOpen()\" (closed)=\"drawerOpen.set(false)\" />\n *\n * Raw server text is never shown: the wording comes from `lastError.kind`. Discards ask first (ConfirmService). Results are announced\n * in a polite live region that exists from the first render. Test ids: pending-drawer-live, pending-drawer-auth, pending-drawer-list,\n * pending-drawer-empty, pending-row-<id>, pending-row-status-<id>, pending-row-error-<id>, pending-row-time-<id>, pending-row-retry-<id>,\n * pending-row-discard-<id>, pending-drawer-sync-all, pending-drawer-discard-all.\n */\n@Component({\n selector: 'ui-pending-changes-drawer',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [SidePanelComponent],\n template: `\n <div class=\"pcd-live\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" data-testid=\"pending-drawer-live\">{{ announcement() }}</div>\n <ui-side-panel\n [open]=\"open()\"\n [title]=\"labels.t('connection.drawer.title')\"\n [cancelLabel]=\"labels.t('connection.drawer.close')\"\n [primaryLabel]=\"labels.t('connection.syncAll')\"\n [primaryDisabled]=\"rows().length === 0 || support.syncing()\"\n [primaryLoading]=\"support.syncing()\"\n primaryTestId=\"pending-drawer-sync-all\"\n (closed)=\"closed.emit()\"\n (primaryClick)=\"syncAll()\">\n @if (support.authBlocked()) {\n <p class=\"pcd-auth\" data-testid=\"pending-drawer-auth\">{{ labels.t('connection.authBlocked') }}</p>\n }\n @if (rows().length === 0) {\n <p class=\"pcd-empty\" tabindex=\"-1\" data-testid=\"pending-drawer-empty\">{{ support.storageAvailable() ? labels.t('connection.drawer.empty') : labels.t('connection.storageUnavailable') }}</p>\n } @else {\n <ul class=\"pcd-list\" [attr.aria-label]=\"labels.t('connection.drawer.list')\" data-testid=\"pending-drawer-list\">\n @for (row of rows(); track row.id) {\n <li class=\"pcd-row\" [attr.data-testid]=\"'pending-row-' + row.id\" [attr.data-status]=\"row.status\">\n <div class=\"pcd-main\">\n <span class=\"pcd-label\">{{ row.label }}</span>\n <span class=\"pcd-status\" [attr.data-testid]=\"'pending-row-status-' + row.id\">{{ statusText(row) }}</span>\n </div>\n <time class=\"pcd-time\" [attr.datetime]=\"iso(row)\" [attr.title]=\"absolute(row)\" [attr.data-testid]=\"'pending-row-time-' + row.id\">{{ savedText(row) }}</time>\n @if (errorText(row); as err) {\n <p class=\"pcd-error\" [attr.data-testid]=\"'pending-row-error-' + row.id\">{{ err }}</p>\n }\n <div class=\"pcd-actions\">\n @if (canRetry(row)) {\n <button type=\"button\" class=\"pcd-btn\" [attr.data-testid]=\"'pending-row-retry-' + row.id\"\n [attr.aria-label]=\"labels.t('connection.retryNamed', { label: row.label })\" (click)=\"retry(row)\">{{ labels.t('connection.retry') }}</button>\n }\n <button type=\"button\" class=\"pcd-btn pcd-btn-danger\" [attr.data-testid]=\"'pending-row-discard-' + row.id\" [disabled]=\"row.status === 'syncing'\"\n [attr.aria-label]=\"labels.t('connection.discardNamed', { label: row.label })\" (click)=\"discard(row)\">{{ labels.t('connection.discard') }}</button>\n </div>\n </li>\n }\n </ul>\n }\n <button panel-footer-extra type=\"button\" class=\"pcd-btn pcd-btn-danger\" data-testid=\"pending-drawer-discard-all\"\n [disabled]=\"rows().length === 0 || support.syncing()\" (click)=\"discardAll()\">{{ labels.t('connection.discardAll') }}</button>\n </ui-side-panel>\n `,\n styles: [`\n :host { display: contents; }\n .pcd-live { position: absolute; inline-size: 1px; block-size: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }\n .pcd-auth { margin: 0 0 16px; padding: 12px 14px; border-radius: 8px; border: 1px solid #b91c1c; background: #fee2e2; color: #7f1d1d; font-size: 0.875rem; }\n .pcd-empty { margin: 0; padding: 24px 0; color: #475569; font-size: 0.9375rem; text-align: center; }\n .pcd-empty:focus { outline: none; }\n .pcd-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }\n .pcd-row { padding: 12px 14px; border: 1px solid #e2e8f0; border-radius: 10px; background: #fff; display: flex; flex-direction: column; gap: 6px; }\n .pcd-main { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 12px; }\n .pcd-label { font-weight: 600; color: #0f172a; overflow-wrap: anywhere; min-inline-size: 0; }\n .pcd-status { font-size: 0.8125rem; color: #334155; }\n .pcd-time { font-size: 0.8125rem; color: #475569; }\n .pcd-error { margin: 0; font-size: 0.875rem; color: #7f1d1d; }\n .pcd-row[data-status='conflict'] .pcd-error, .pcd-row[data-status='failed'] .pcd-error { color: #78350f; }\n .pcd-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-block-start: 4px; }\n .pcd-btn { min-block-size: 44px; min-inline-size: 44px; padding: 0 16px; border-radius: 8px; border: 1px solid #334155; background: #fff; color: #0f172a;\n font: inherit; font-size: 0.875rem; font-weight: 600; cursor: pointer; -webkit-appearance: none; appearance: none; }\n .pcd-btn-danger { border-color: #b91c1c; color: #b91c1c; }\n .pcd-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n .pcd-btn:focus { outline: 3px solid #1d4ed8; outline-offset: 2px; }\n .pcd-btn:focus:not(:focus-visible) { outline: none; }\n @media (max-width: 480px) { .pcd-actions { flex-direction: column; } .pcd-btn { inline-size: 100%; } }\n @media (forced-colors: active) { .pcd-row, .pcd-auth, .pcd-btn { border: 1px solid CanvasText; background: Canvas; color: CanvasText; } }\n `],\n})\nexport class PendingChangesDrawerComponent {\n protected readonly labels = inject(UiLabelsService);\n protected readonly support = inject(OfflineSupportService);\n private readonly confirmService = inject(ConfirmService);\n private readonly cdr = inject(ChangeDetectorRef);\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly doc = inject(DOCUMENT);\n\n readonly open = input(false);\n readonly closed = output<void>();\n\n protected readonly rows = computed(() => this.support.rows());\n protected readonly announcement = signal('');\n private readonly now = signal(Date.now());\n\n constructor() {\n const timer = setInterval(() => this.now.set(Date.now()), 30_000);\n inject(DestroyRef).onDestroy(() => clearInterval(timer));\n }\n\n protected statusText(row: PendingRow): string {\n switch (row.status) {\n case 'syncing': return this.labels.t('connection.status.syncing');\n case 'failed': return this.labels.t('connection.status.failed');\n case 'conflict': return this.labels.t('connection.status.conflict');\n default: return this.labels.t('connection.status.pending');\n }\n }\n\n /** Plain words from the error KIND; the server's own text (`lastError.message`) is never shown. */\n protected errorText(row: PendingRow): string {\n const kind = (row.lastError?.kind as ErrorKind | undefined) ?? (row.status === 'conflict' ? 'conflict' : undefined);\n if (kind && kind in ERROR_KEYS) return this.labels.t(ERROR_KEYS[kind]);\n if (row.status === 'failed' || row.lastError) return this.labels.t('connection.error.unknown');\n return '';\n }\n\n protected canRetry(row: PendingRow): boolean {\n return row.status === 'failed' || row.status === 'conflict' || (row.status === 'pending' && !!row.lastError);\n }\n\n protected iso(row: PendingRow): string { return new Date(row.createdAt).toISOString(); }\n protected absolute(row: PendingRow): string { return absoluteTime(row.createdAt, pageLocale(this.doc, this.labels.locale())); }\n protected savedText(row: PendingRow): string {\n const time = formatRelativeTime(row.createdAt, this.now(), this.labels.t('connection.time.justNow'), pageLocale(this.doc, this.labels.locale()));\n return this.labels.t('connection.savedAt', { time });\n }\n\n protected async retry(row: PendingRow): Promise<void> {\n this.announcement.set(this.labels.t('connection.result.retrying', { label: row.label }));\n try { await this.support.retry(row.id); } catch { this.announceFailure(); }\n }\n\n protected async syncAll(): Promise<void> {\n this.announcement.set(this.labels.t('connection.result.syncStarted'));\n try { await this.support.syncNow(); } catch { this.announceFailure(); }\n }\n\n protected async discard(row: PendingRow): Promise<void> {\n const index = this.rows().findIndex(r => r.id === row.id);\n // Other saved changes that point at a record this change creates cannot be sent once it is gone: say so before the user decides.\n // The count follows chains (create A, create B that uses A, an edit of B: discarding A fails both B and the edit); the set also guards against cycles.\n const all = this.rows();\n const dependentIds = new Set<string>();\n const pending = [row.id];\n while (pending.length > 0) {\n const current = pending.pop() as string;\n for (const r of all) {\n if (r.id !== row.id && !dependentIds.has(r.id) && (r.dependsOn ?? []).includes(current)) { dependentIds.add(r.id); pending.push(r.id); }\n }\n }\n const dependents = dependentIds.size;\n const message = dependents === 0 ? this.labels.t('connection.discard.message', { label: row.label })\n : dependents === 1 ? this.labels.t('connection.discard.dependents.one', { label: row.label })\n : this.labels.t('connection.discard.dependents.other', { label: row.label, count: dependents });\n const ok = await this.confirmService.confirm({\n title: this.labels.t('connection.discard.title'),\n message,\n confirmLabel: this.labels.t('connection.discard'),\n cancelLabel: this.labels.t('toolbar.cancel'),\n variant: 'danger',\n });\n if (!ok) return;\n try { await this.support.discard(row.id); } catch { this.announceFailure(); return; }\n this.announcement.set(this.labels.t('connection.result.discarded', { label: row.label }));\n this.cdr.detectChanges();\n this.focusAfterRemoval(index);\n }\n\n protected async discardAll(): Promise<void> {\n const count = this.rows().length;\n if (count === 0) return;\n const ok = await this.confirmService.confirm({\n title: this.labels.t('connection.discardAll.title'),\n message: this.labels.t(count === 1 ? 'connection.discardAll.message.one' : 'connection.discardAll.message.other', { count }),\n confirmLabel: this.labels.t('connection.discardAll'),\n cancelLabel: this.labels.t('toolbar.cancel'),\n variant: 'danger',\n });\n if (!ok) return;\n try { await this.support.discardAll(); } catch { this.announceFailure(); return; }\n this.announcement.set(this.labels.t('connection.result.discardedAll'));\n this.cdr.detectChanges();\n this.focusAfterRemoval(0);\n }\n\n /** A rejected service call must not become an unhandled rejection; tell the user politely (nothing was lost). */\n private announceFailure(): void {\n this.announcement.set(this.labels.t('connection.error.unknown'));\n }\n\n /** The row the user acted on is gone: move focus to the row now at that position (or the previous one, or the empty message). */\n private focusAfterRemoval(index: number): void {\n // ui-side-panel projects its body into the panel element, which lives under this component's host.\n const root = this.host.nativeElement;\n const rowEls = Array.from(root.querySelectorAll<HTMLElement>('li.pcd-row'));\n const target = rowEls[Math.min(index, rowEls.length - 1)]?.querySelector<HTMLElement>('button:not([disabled])') ?? root.querySelector<HTMLElement>('.pcd-empty');\n target?.focus();\n }\n}\n","import { ChangeDetectionStrategy, Component, DestroyRef, computed, inject, output, signal } from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\nimport { UiLabelsService } from '@forgeroute/ui';\nimport { StaleDataService } from '../deps';\nimport { absoluteTime, formatRelativeTime, pageLocale } from '../relative-time';\n\nconst TIME_MARK = '\\u0001';\n\n/**\n * \"Showing saved data from 5 minutes ago [Refresh]\" while `StaleDataService.staleAt()` is set (a GET was answered from the\n * device copy). Put it at the top of the page area that shows the data; the app reloads the data on `(refresh)`:\n *\n * <ui-stale-data-chip (refresh)=\"reload()\" />\n *\n * A polite live region exists from the first render (visually hidden) and mirrors the sentence. Test ids: stale-data-chip,\n * stale-data-chip-live, stale-data-chip-time, stale-data-chip-refresh.\n */\n@Component({\n selector: 'ui-stale-data-chip',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"sdc-live\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" data-testid=\"stale-data-chip-live\">{{ announcement() }}</div>\n @if (parts(); as p) {\n <div class=\"sdc\" data-testid=\"stale-data-chip\">\n <span class=\"sdc-text\">{{ p.before }}<time [attr.datetime]=\"p.iso\" [attr.title]=\"p.absolute\" data-testid=\"stale-data-chip-time\">{{ p.relative }}</time>{{ p.after }}</span>\n <button type=\"button\" class=\"sdc-btn\" data-testid=\"stale-data-chip-refresh\" [attr.aria-label]=\"labels.t('connection.refreshAria')\" (click)=\"refresh.emit()\">{{ labels.t('connection.refresh') }}</button>\n </div>\n }\n `,\n styles: [`\n :host { display: block; position: relative; }\n .sdc-live { position: absolute; inline-size: 1px; block-size: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }\n .sdc { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; padding: 6px 8px 6px 14px; border-radius: 999px; border: 1px solid #b45309;\n background: #fef3c7; color: #78350f; font-size: 0.8125rem; line-height: 1.4; max-inline-size: 100%; box-sizing: border-box; }\n .sdc-text { min-inline-size: 0; overflow-wrap: anywhere; }\n .sdc-btn { min-block-size: 44px; min-inline-size: 44px; padding: 0 14px; border-radius: 999px; border: 1px solid currentColor; background: #fff; color: inherit;\n font: inherit; font-weight: 600; cursor: pointer; -webkit-appearance: none; appearance: none; }\n .sdc-btn:focus { outline: 3px solid #1d4ed8; outline-offset: 2px; }\n .sdc-btn:focus:not(:focus-visible) { outline: none; }\n @media (max-width: 480px) { .sdc { border-radius: 12px; } .sdc-btn { inline-size: 100%; } }\n @media (forced-colors: active) { .sdc, .sdc-btn { border: 1px solid CanvasText; background: Canvas; color: CanvasText; } }\n `],\n})\nexport class StaleDataChipComponent {\n protected readonly labels = inject(UiLabelsService);\n private readonly stale = inject(StaleDataService);\n private readonly doc = inject(DOCUMENT);\n /** The user pressed Refresh - reload the data from the server. */\n readonly refresh = output<void>();\n\n private readonly now = signal(Date.now());\n\n protected readonly parts = computed(() => {\n const at = this.stale.staleAt();\n if (at == null) return null;\n const locale = pageLocale(this.doc, this.labels.locale());\n const relative = formatRelativeTime(at, this.now(), this.labels.t('connection.time.justNow'), locale);\n const text = this.labels.t('connection.stale', { time: TIME_MARK });\n const at0 = text.indexOf(TIME_MARK);\n // A translation without the {{ time }} placeholder still shows the time, after the text and a space (never glued to the last word).\n const before = at0 < 0 ? `${text} ` : text.slice(0, at0);\n const after = at0 < 0 ? '' : text.slice(at0 + TIME_MARK.length);\n return { before, after, relative, iso: new Date(at).toISOString(), absolute: absoluteTime(at, locale) };\n });\n\n protected readonly announcement = computed(() => {\n const p = this.parts();\n return p ? `${p.before}${p.relative}${p.after}` : '';\n });\n\n constructor() {\n // Keep \"5 minutes ago\" current while the page stays open.\n const timer = setInterval(() => this.now.set(Date.now()), 30_000);\n inject(DestroyRef).onDestroy(() => clearInterval(timer));\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, effect, inject, input, signal, untracked } from '@angular/core';\nimport { UiLabelsService } from '@forgeroute/ui';\n\n/**\n * \"Still working… please don't tap again.\" for a button or panel whose request has been running for a while.\n *\n * <ui-still-working [active]=\"saving()\" />\n *\n * The host keeps its own button disabled while `active`. The text appears only after `delayMs` (default 4 s) of continuous\n * `active`, disappears at once when `active` turns false, and sits in a polite live region that exists from the first render\n * (so screen readers announce the text when it appears). Test id: still-working.\n */\n@Component({\n selector: 'ui-still-working',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `<div class=\"sw\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" data-testid=\"still-working\">{{ text() }}</div>`,\n styles: [`\n :host { display: block; }\n .sw { color: #475569; font-size: 0.8125rem; line-height: 1.4; }\n .sw:empty { min-block-size: 0; margin: 0; padding: 0; }\n @media (forced-colors: active) { .sw { color: CanvasText; } }\n `],\n})\nexport class StillWorkingComponent {\n private readonly labels = inject(UiLabelsService);\n /** True while the request runs. */\n readonly active = input(false);\n /** Milliseconds of continuous `active` before the text appears. */\n readonly delayMs = input(4000);\n\n private readonly elapsed = signal(false);\n protected readonly text = computed(() => (this.active() && this.elapsed() ? this.labels.t('connection.stillWorking') : ''));\n\n constructor() {\n effect(onCleanup => {\n const active = this.active();\n const delay = this.delayMs();\n untracked(() => this.elapsed.set(false));\n if (!active) return;\n const timer = setTimeout(() => this.elapsed.set(true), Math.max(0, delay));\n onCleanup(() => clearTimeout(timer));\n });\n }\n}\n"],"mappings":"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"}
|