@rockhall/electron-offline-content 0.4.0

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.
Files changed (196) hide show
  1. package/CHANGELOG.md +384 -0
  2. package/LICENSE +21 -0
  3. package/README.md +794 -0
  4. package/dist/internal/asset-file-name.cjs +13 -0
  5. package/dist/internal/asset-file-name.cjs.map +1 -0
  6. package/dist/internal/asset-file-name.d.cts +6 -0
  7. package/dist/internal/asset-file-name.d.cts.map +1 -0
  8. package/dist/internal/asset-file-name.d.ts +6 -0
  9. package/dist/internal/asset-file-name.d.ts.map +1 -0
  10. package/dist/internal/asset-file-name.js +12 -0
  11. package/dist/internal/asset-file-name.js.map +1 -0
  12. package/dist/internal/asset-key.cjs +30 -0
  13. package/dist/internal/asset-key.cjs.map +1 -0
  14. package/dist/internal/asset-key.d.cts +19 -0
  15. package/dist/internal/asset-key.d.cts.map +1 -0
  16. package/dist/internal/asset-key.d.ts +19 -0
  17. package/dist/internal/asset-key.d.ts.map +1 -0
  18. package/dist/internal/asset-key.js +27 -0
  19. package/dist/internal/asset-key.js.map +1 -0
  20. package/dist/internal/log-format.cjs +98 -0
  21. package/dist/internal/log-format.cjs.map +1 -0
  22. package/dist/internal/log-format.d.cts +10 -0
  23. package/dist/internal/log-format.d.cts.map +1 -0
  24. package/dist/internal/log-format.d.ts +10 -0
  25. package/dist/internal/log-format.d.ts.map +1 -0
  26. package/dist/internal/log-format.js +97 -0
  27. package/dist/internal/log-format.js.map +1 -0
  28. package/dist/internal/media-kind.cjs +46 -0
  29. package/dist/internal/media-kind.cjs.map +1 -0
  30. package/dist/internal/media-kind.d.cts +20 -0
  31. package/dist/internal/media-kind.d.cts.map +1 -0
  32. package/dist/internal/media-kind.d.ts +20 -0
  33. package/dist/internal/media-kind.d.ts.map +1 -0
  34. package/dist/internal/media-kind.js +45 -0
  35. package/dist/internal/media-kind.js.map +1 -0
  36. package/dist/internal/url-warn.cjs +14 -0
  37. package/dist/internal/url-warn.cjs.map +1 -0
  38. package/dist/internal/url-warn.d.cts +10 -0
  39. package/dist/internal/url-warn.d.cts.map +1 -0
  40. package/dist/internal/url-warn.d.ts +10 -0
  41. package/dist/internal/url-warn.d.ts.map +1 -0
  42. package/dist/internal/url-warn.js +13 -0
  43. package/dist/internal/url-warn.js.map +1 -0
  44. package/dist/internal/validation.cjs +222 -0
  45. package/dist/internal/validation.cjs.map +1 -0
  46. package/dist/internal/validation.d.cts +78 -0
  47. package/dist/internal/validation.d.cts.map +1 -0
  48. package/dist/internal/validation.d.ts +78 -0
  49. package/dist/internal/validation.d.ts.map +1 -0
  50. package/dist/internal/validation.js +196 -0
  51. package/dist/internal/validation.js.map +1 -0
  52. package/dist/main/asset-download.cjs +265 -0
  53. package/dist/main/asset-download.cjs.map +1 -0
  54. package/dist/main/asset-download.d.cts +12 -0
  55. package/dist/main/asset-download.d.cts.map +1 -0
  56. package/dist/main/asset-download.d.ts +12 -0
  57. package/dist/main/asset-download.d.ts.map +1 -0
  58. package/dist/main/asset-download.js +263 -0
  59. package/dist/main/asset-download.js.map +1 -0
  60. package/dist/main/database.cjs +473 -0
  61. package/dist/main/database.cjs.map +1 -0
  62. package/dist/main/database.d.cts +81 -0
  63. package/dist/main/database.d.cts.map +1 -0
  64. package/dist/main/database.d.ts +81 -0
  65. package/dist/main/database.d.ts.map +1 -0
  66. package/dist/main/database.js +472 -0
  67. package/dist/main/database.js.map +1 -0
  68. package/dist/main/index.cjs +22 -0
  69. package/dist/main/index.d.cts +7 -0
  70. package/dist/main/index.d.ts +7 -0
  71. package/dist/main/index.js +7 -0
  72. package/dist/main/media-cache.cjs +862 -0
  73. package/dist/main/media-cache.cjs.map +1 -0
  74. package/dist/main/media-cache.d.cts +134 -0
  75. package/dist/main/media-cache.d.cts.map +1 -0
  76. package/dist/main/media-cache.d.ts +134 -0
  77. package/dist/main/media-cache.d.ts.map +1 -0
  78. package/dist/main/media-cache.js +854 -0
  79. package/dist/main/media-cache.js.map +1 -0
  80. package/dist/main/storage-root-lock.cjs +124 -0
  81. package/dist/main/storage-root-lock.cjs.map +1 -0
  82. package/dist/main/storage-root-lock.d.cts +11 -0
  83. package/dist/main/storage-root-lock.d.cts.map +1 -0
  84. package/dist/main/storage-root-lock.d.ts +11 -0
  85. package/dist/main/storage-root-lock.d.ts.map +1 -0
  86. package/dist/main/storage-root-lock.js +120 -0
  87. package/dist/main/storage-root-lock.js.map +1 -0
  88. package/dist/main/store.cjs +197 -0
  89. package/dist/main/store.cjs.map +1 -0
  90. package/dist/main/store.d.cts +83 -0
  91. package/dist/main/store.d.cts.map +1 -0
  92. package/dist/main/store.d.ts +83 -0
  93. package/dist/main/store.d.ts.map +1 -0
  94. package/dist/main/store.js +195 -0
  95. package/dist/main/store.js.map +1 -0
  96. package/dist/preload/index.cjs +36 -0
  97. package/dist/preload/index.cjs.map +1 -0
  98. package/dist/preload/index.d.cts +14 -0
  99. package/dist/preload/index.d.cts.map +1 -0
  100. package/dist/preload/index.d.ts +14 -0
  101. package/dist/preload/index.d.ts.map +1 -0
  102. package/dist/preload/index.js +34 -0
  103. package/dist/preload/index.js.map +1 -0
  104. package/dist/react/index.cjs +199 -0
  105. package/dist/react/index.cjs.map +1 -0
  106. package/dist/react/index.d.cts +50 -0
  107. package/dist/react/index.d.cts.map +1 -0
  108. package/dist/react/index.d.ts +50 -0
  109. package/dist/react/index.d.ts.map +1 -0
  110. package/dist/react/index.js +191 -0
  111. package/dist/react/index.js.map +1 -0
  112. package/dist/renderer/helpers.cjs +36 -0
  113. package/dist/renderer/helpers.cjs.map +1 -0
  114. package/dist/renderer/helpers.d.cts +11 -0
  115. package/dist/renderer/helpers.d.cts.map +1 -0
  116. package/dist/renderer/helpers.d.ts +11 -0
  117. package/dist/renderer/helpers.d.ts.map +1 -0
  118. package/dist/renderer/helpers.js +35 -0
  119. package/dist/renderer/helpers.js.map +1 -0
  120. package/dist/renderer/index.cjs +20 -0
  121. package/dist/renderer/index.cjs.map +1 -0
  122. package/dist/renderer/index.d.cts +14 -0
  123. package/dist/renderer/index.d.cts.map +1 -0
  124. package/dist/renderer/index.d.ts +14 -0
  125. package/dist/renderer/index.d.ts.map +1 -0
  126. package/dist/renderer/index.js +14 -0
  127. package/dist/renderer/index.js.map +1 -0
  128. package/dist/renderer/runtime.cjs +278 -0
  129. package/dist/renderer/runtime.cjs.map +1 -0
  130. package/dist/renderer/runtime.d.cts +35 -0
  131. package/dist/renderer/runtime.d.cts.map +1 -0
  132. package/dist/renderer/runtime.d.ts +35 -0
  133. package/dist/renderer/runtime.d.ts.map +1 -0
  134. package/dist/renderer/runtime.js +273 -0
  135. package/dist/renderer/runtime.js.map +1 -0
  136. package/dist/renderer/window-globals.d.cts +9 -0
  137. package/dist/renderer/window-globals.d.cts.map +1 -0
  138. package/dist/renderer/window-globals.d.ts +9 -0
  139. package/dist/renderer/window-globals.d.ts.map +1 -0
  140. package/dist/shared/errors.cjs +102 -0
  141. package/dist/shared/errors.cjs.map +1 -0
  142. package/dist/shared/errors.d.cts +45 -0
  143. package/dist/shared/errors.d.cts.map +1 -0
  144. package/dist/shared/errors.d.ts +45 -0
  145. package/dist/shared/errors.d.ts.map +1 -0
  146. package/dist/shared/errors.js +93 -0
  147. package/dist/shared/errors.js.map +1 -0
  148. package/dist/shared/ipc.cjs +14 -0
  149. package/dist/shared/ipc.cjs.map +1 -0
  150. package/dist/shared/ipc.d.cts +12 -0
  151. package/dist/shared/ipc.d.cts.map +1 -0
  152. package/dist/shared/ipc.d.ts +12 -0
  153. package/dist/shared/ipc.d.ts.map +1 -0
  154. package/dist/shared/ipc.js +13 -0
  155. package/dist/shared/ipc.js.map +1 -0
  156. package/dist/shared/normalize.cjs +19 -0
  157. package/dist/shared/normalize.cjs.map +1 -0
  158. package/dist/shared/normalize.d.cts +11 -0
  159. package/dist/shared/normalize.d.cts.map +1 -0
  160. package/dist/shared/normalize.d.ts +11 -0
  161. package/dist/shared/normalize.d.ts.map +1 -0
  162. package/dist/shared/normalize.js +18 -0
  163. package/dist/shared/normalize.js.map +1 -0
  164. package/dist/shared/pagination.cjs +32 -0
  165. package/dist/shared/pagination.cjs.map +1 -0
  166. package/dist/shared/pagination.d.cts +14 -0
  167. package/dist/shared/pagination.d.cts.map +1 -0
  168. package/dist/shared/pagination.d.ts +14 -0
  169. package/dist/shared/pagination.d.ts.map +1 -0
  170. package/dist/shared/pagination.js +28 -0
  171. package/dist/shared/pagination.js.map +1 -0
  172. package/dist/shared/stem.cjs +16 -0
  173. package/dist/shared/stem.cjs.map +1 -0
  174. package/dist/shared/stem.d.cts +6 -0
  175. package/dist/shared/stem.d.cts.map +1 -0
  176. package/dist/shared/stem.d.ts +6 -0
  177. package/dist/shared/stem.d.ts.map +1 -0
  178. package/dist/shared/stem.js +14 -0
  179. package/dist/shared/stem.js.map +1 -0
  180. package/dist/shared/types.cjs +15 -0
  181. package/dist/shared/types.cjs.map +1 -0
  182. package/dist/shared/types.d.cts +234 -0
  183. package/dist/shared/types.d.cts.map +1 -0
  184. package/dist/shared/types.d.ts +234 -0
  185. package/dist/shared/types.d.ts.map +1 -0
  186. package/dist/shared/types.js +14 -0
  187. package/dist/shared/types.js.map +1 -0
  188. package/package.json +120 -0
  189. package/skills/authenticated-downloads/SKILL.md +203 -0
  190. package/skills/cache-configuration/SKILL.md +357 -0
  191. package/skills/cache-configuration/references/options.md +356 -0
  192. package/skills/getting-started/SKILL.md +407 -0
  193. package/skills/production-checklist/SKILL.md +397 -0
  194. package/skills/react-rendering/SKILL.md +424 -0
  195. package/skills/react-rendering/references/hooks.md +443 -0
  196. package/skills/store-authoring/SKILL.md +369 -0
@@ -0,0 +1,397 @@
1
+ ---
2
+ name: production-checklist
3
+ description: >
4
+ Go-live audit for kiosk deployment: verify devPassthrough disabled,
5
+ storage limits set for device hardware (maxCacheBytes, reserveFreeBytes),
6
+ structured logging wired to production sink, onSyncFailure mode chosen,
7
+ app.requestSingleInstanceLock in place, scope boundaries confirmed
8
+ (read-only presentation assets only), offline mode tested with real
9
+ storage path and media:// protocol URLs, disk space validated for
10
+ full catalog download.
11
+ type: lifecycle
12
+ library: electron-offline-content
13
+ library_version: "0.4.0"
14
+ requires:
15
+ - cache-configuration
16
+ sources:
17
+ - "rockhallweb/electron-offline-content:src/main/media-cache.ts"
18
+ - "rockhallweb/electron-offline-content:src/shared/types.ts"
19
+ - "rockhallweb/electron-offline-content:src/main/storage-root-lock.ts"
20
+ - "rockhallweb/electron-offline-content:README.md"
21
+ ---
22
+
23
+ # Production Checklist
24
+
25
+ This skill builds on cache-configuration. Read it first for all createMediaCache options.
26
+
27
+ ## Runtime Configuration Checks
28
+
29
+ ### Check: devPassthrough is explicitly disabled
30
+
31
+ **Expected:**
32
+
33
+ ```typescript
34
+ import { createMediaCache } from "@rockhall/electron-offline-content/main";
35
+
36
+ const mediaCache = createMediaCache({
37
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
38
+ devPassthrough: false,
39
+ resolveStore: async () => store,
40
+ });
41
+ ```
42
+
43
+ **Fail condition:** `NODE_ENV` is `"development"` in production build, or `devPassthrough` is omitted and `NODE_ENV` is not set. Defaults to `false` when `NODE_ENV` is unset, but being explicit prevents silent regressions if build tooling sets `NODE_ENV`.
44
+
45
+ **Fix:** Set `devPassthrough: false` explicitly in production configuration.
46
+
47
+ ### Check: onSyncFailure mode is chosen deliberately
48
+
49
+ **Expected — kiosk that must always show content:**
50
+
51
+ ```typescript
52
+ const mediaCache = createMediaCache({
53
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
54
+ devPassthrough: false,
55
+ onSyncFailure: "serve-last-snapshot",
56
+ resolveStore: async () => store,
57
+ });
58
+ ```
59
+
60
+ **Expected — app that needs fresh content:**
61
+
62
+ ```typescript
63
+ const mediaCache = createMediaCache({
64
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
65
+ devPassthrough: false,
66
+ onSyncFailure: "throw",
67
+ resolveStore: async () => store,
68
+ });
69
+ ```
70
+
71
+ **Fail condition:** `onSyncFailure` omitted — default may not match your use case. Stale content on a kiosk loop may be acceptable; stale content on an info display with time-sensitive data may not.
72
+
73
+ **Fix:** Set explicitly based on whether stale content or a blank screen is worse for your deployment.
74
+
75
+ ### Check: app.requestSingleInstanceLock() is in place
76
+
77
+ **Expected:**
78
+
79
+ ```typescript
80
+ import { app } from "electron";
81
+ import { createMediaCache } from "@rockhall/electron-offline-content/main";
82
+
83
+ if (!app.requestSingleInstanceLock()) {
84
+ app.exit(1);
85
+ }
86
+
87
+ const mediaCache = createMediaCache({
88
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
89
+ devPassthrough: false,
90
+ resolveStore: async () => store,
91
+ });
92
+ ```
93
+
94
+ **Fail condition:** Second process launches and collides on the storage root. `StorageOwnershipError` thrown at `start()` time — kiosk may silently restart into a crash loop.
95
+
96
+ **Fix:** Acquire lock before `createMediaCache`. Exit immediately on failure.
97
+
98
+ ## Storage Checks
99
+
100
+ ### Check: Storage limits match device hardware
101
+
102
+ **Expected:** `maxCacheBytes` set from SSD capacity. Tune `reserveFreeBytes` for OS, logs, and other apps (the library defaults to **1 GiB** when omitted, but kiosks often need a larger explicit cushion).
103
+
104
+ ```typescript
105
+ const mediaCache = createMediaCache({
106
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
107
+ devPassthrough: false,
108
+ maxCacheBytes: 10 * 1024 * 1024 * 1024,
109
+ reserveFreeBytes: 5 * 1024 * 1024 * 1024,
110
+ resolveStore: async () => store,
111
+ });
112
+ ```
113
+
114
+ **Fail condition:** Cache consumes all disk on kiosk SSD. OS becomes unresponsive, other apps crash, kiosk requires physical intervention.
115
+
116
+ **Fix:** Set `maxCacheBytes` based on device SSD capacity. Set `reserveFreeBytes` explicitly when the default 1 GiB is too small for your platform. Example: 128 GB SSD kiosk → `maxCacheBytes: 80 * 1024 * 1024 * 1024`, `reserveFreeBytes: 5 * 1024 * 1024 * 1024`. Use **`reserveFreeBytes: 0`** only if you intentionally need the old no-reservation behavior.
117
+
118
+ ### Check: storagePath targets a valid writable location
119
+
120
+ **Expected:** `storagePath` uses an `appPath` that is writable on the target platform after packaging.
121
+
122
+ **Fail condition:** Permission errors at runtime on certain platforms or packagers. `"userData"` is generally safe; other paths may not be writable in sandboxed or locked-down kiosk environments.
123
+
124
+ **Fix:** Build and run the packaged app on the target platform. Verify the resolved storage directory is writable. Check with `app.getPath("userData")` in the main process to confirm the actual path.
125
+
126
+ ## Logging Checks
127
+
128
+ ### Check: Structured logging wired to production sink
129
+
130
+ **Expected:**
131
+
132
+ ```typescript
133
+ import pino from "pino";
134
+ import { createMediaCache } from "@rockhall/electron-offline-content/main";
135
+
136
+ const logger = pino({ name: "media-cache" });
137
+
138
+ const mediaCache = createMediaCache({
139
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
140
+ devPassthrough: false,
141
+ logging: {
142
+ level: "info",
143
+ onLog: (entry) => {
144
+ logger[entry.level === "debug" ? "debug" : entry.level](entry, entry.event);
145
+ },
146
+ },
147
+ resolveStore: async () => store,
148
+ });
149
+ ```
150
+
151
+ **Fail condition:** Default console logging is invisible on headless kiosks. Sync failures, disk errors, and download issues go unnoticed until content goes stale.
152
+
153
+ **Fix:** Set `logging.onLog` with your production logger. Wire to pino, logtape, or your log aggregation service. Set `logging.level: "info"` — `"debug"` produces high volume in production.
154
+
155
+ ## Scope Boundary Checks
156
+
157
+ ### Check: Package used only for read-only presentation assets
158
+
159
+ **Expected:** Cache serves media for display — video, images, audio, documents. Content flows one direction: CMS → store → download → local storage → renderer display.
160
+
161
+ **Fail condition:** Package misused for user-generated content (webcam captures, user uploads, form attachments) or runtime data storage. No write operations are exposed to the renderer. The cache is read-only by design.
162
+
163
+ **Fix:** Use a different solution for user-generated content. Electron's `dialog.showSaveDialog`, IndexedDB, or a separate upload service are appropriate for user-created files.
164
+
165
+ ## Testing Checks
166
+
167
+ ### Check: Offline mode tested with real storage path and media:// URLs
168
+
169
+ **Expected:** App built and tested with `devPassthrough: false`. Content loads from `media://` protocol URLs. Renderer displays media correctly from local storage.
170
+
171
+ **Fail condition:**
172
+
173
+ - `storagePath` permissions fail on the target platform after packaging.
174
+ - DOM security policies block `media://` protocol URLs in `<video>`, `<img>`, or `<audio>` elements.
175
+ - Media rendering issues (codec support, file integrity) only surface when serving from disk.
176
+ - All of these are invisible in dev passthrough mode because assets load from remote `https://` URLs.
177
+
178
+ **Fix:** Build the production binary. Run it with `devPassthrough: false`. Navigate every content type. Verify `media://` URLs render in all media elements.
179
+
180
+ ### Check: Full catalog download tested for disk space
181
+
182
+ **Expected:** Download the full store to verify it fits on the target device. Monitor disk usage during and after sync completes.
183
+
184
+ **Fail condition:** Catalog exceeds available disk space on kiosk hardware. Downloads stall or fail when `maxCacheBytes` or `reserveFreeBytes` limits are hit. Partial sync leaves kiosk with incomplete content.
185
+
186
+ **Fix:** For smoke tests, scope down the store to a subset. For production validation, verify the device has capacity for the full catalog plus `reserveFreeBytes` headroom. Calculate: total store size + reserve + OS needs < device disk capacity.
187
+
188
+ ## Common Mistakes
189
+
190
+ ### CRITICAL: devPassthrough left enabled in production
191
+
192
+ In production Electron builds, `NODE_ENV` may be unset — which defaults `devPassthrough` to `false`. But if `NODE_ENV` is explicitly set to `"development"` in a deployed build (common with misconfigured build tooling), all downloads are silently skipped and the app serves only remote URLs. The kiosk works on the network but fails offline.
193
+
194
+ Wrong — relying on `NODE_ENV`:
195
+
196
+ ```typescript
197
+ const mediaCache = createMediaCache({
198
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
199
+ resolveStore: async () => store,
200
+ });
201
+ ```
202
+
203
+ Correct — explicit:
204
+
205
+ ```typescript
206
+ const mediaCache = createMediaCache({
207
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
208
+ devPassthrough: false,
209
+ resolveStore: async () => store,
210
+ });
211
+ ```
212
+
213
+ Source: media-cache.ts; types.ts
214
+ Cross-skill: also in cache-configuration/SKILL.md § Common Mistakes
215
+
216
+ ### CRITICAL: Shipping with only dev passthrough testing
217
+
218
+ `devPassthrough` bypasses real storage, protocol serving, and filesystem permissions. Platform-specific issues only surface in offline mode:
219
+
220
+ - `media://` protocol URLs may be blocked by CSP or DOM security in certain Electron configurations.
221
+ - Disk space for the full catalog is untested.
222
+ - Authenticated download URLs embedded in `resolveStore` are untested.
223
+ - `onSyncFailure: "serve-last-snapshot"` is overridden to `"throw"` — resilience logic is untested.
224
+
225
+ Wrong — testing only in dev mode:
226
+
227
+ ```typescript
228
+ const mediaCache = createMediaCache({
229
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
230
+ devPassthrough: true,
231
+ resolveStore: async () => store,
232
+ });
233
+ // "It works!" — but only because assets load from remote URLs
234
+ ```
235
+
236
+ Correct — test with production configuration before deploying:
237
+
238
+ ```typescript
239
+ const mediaCache = createMediaCache({
240
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
241
+ devPassthrough: false,
242
+ onSyncFailure: "serve-last-snapshot",
243
+ resolveStore: async () => store,
244
+ });
245
+ // Build, run, verify media:// URLs render, verify sync completes
246
+ ```
247
+
248
+ Source: Maintainer interview
249
+
250
+ ### HIGH: No soft cap on cache size (`maxCacheBytes`)
251
+
252
+ Without `maxCacheBytes`, the cache can grow to the full store size subject only to disk space and the default **1 GiB** `reserveFreeBytes` headroom. On kiosk SSDs (often 64–256 GB), a growing catalog can still consume almost all disk space. Set `maxCacheBytes` explicitly for a hard ceiling.
253
+
254
+ Wrong:
255
+
256
+ ```typescript
257
+ const mediaCache = createMediaCache({
258
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
259
+ devPassthrough: false,
260
+ resolveStore: async () => store,
261
+ });
262
+ ```
263
+
264
+ Correct:
265
+
266
+ ```typescript
267
+ const mediaCache = createMediaCache({
268
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
269
+ devPassthrough: false,
270
+ maxCacheBytes: 10 * 1024 * 1024 * 1024,
271
+ reserveFreeBytes: 1 * 1024 * 1024 * 1024,
272
+ resolveStore: async () => store,
273
+ });
274
+ ```
275
+
276
+ Source: README
277
+
278
+ ### HIGH: Switching to offline mode without considering disk space
279
+
280
+ When disabling `devPassthrough` for the first time, the full catalog downloads to the local machine. Developers on laptops with limited free space may run out of disk during the first sync. Large production catalogs (tens of GB of video) amplify this.
281
+
282
+ Wrong — disabling passthrough without checking space:
283
+
284
+ ```typescript
285
+ // Was devPassthrough: true during development, now switching for production testing
286
+ const mediaCache = createMediaCache({
287
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
288
+ devPassthrough: false,
289
+ resolveStore: async () => fullProductionStore,
290
+ });
291
+ ```
292
+
293
+ Correct — scope down store for local testing:
294
+
295
+ ```typescript
296
+ const mediaCache = createMediaCache({
297
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
298
+ devPassthrough: false,
299
+ maxCacheBytes: 2 * 1024 * 1024 * 1024,
300
+ resolveStore: async () => scopedTestStore,
301
+ });
302
+ ```
303
+
304
+ Source: Maintainer interview
305
+
306
+ ### MEDIUM: Default console logging in production
307
+
308
+ When `logging.onLog` is omitted and `NODE_ENV !== "production"`, the package prints to `console`. On headless kiosks, console output goes nowhere. Sync failures, download errors, and storage warnings are silently lost.
309
+
310
+ Wrong:
311
+
312
+ ```typescript
313
+ const mediaCache = createMediaCache({
314
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
315
+ devPassthrough: false,
316
+ resolveStore: async () => store,
317
+ });
318
+ ```
319
+
320
+ Correct:
321
+
322
+ ```typescript
323
+ import pino from "pino";
324
+
325
+ const logger = pino({ name: "media-cache" });
326
+
327
+ const mediaCache = createMediaCache({
328
+ storagePath: { appPath: "userData", segments: ["offline-media"] },
329
+ devPassthrough: false,
330
+ logging: {
331
+ level: "info",
332
+ onLog: (entry) => {
333
+ logger[entry.level === "debug" ? "debug" : entry.level](entry, entry.event);
334
+ },
335
+ },
336
+ resolveStore: async () => store,
337
+ });
338
+ ```
339
+
340
+ Source: media-cache.ts; README
341
+
342
+ ### MEDIUM: Using package for user-generated content
343
+
344
+ Wrong:
345
+
346
+ ```typescript
347
+ ipcMain.handle("save-webcam-photo", async (_event, photoBuffer: Buffer) => {
348
+ await mediaCache.writeAsset("user-photos", "selfie", photoBuffer);
349
+ });
350
+ ```
351
+
352
+ Correct:
353
+
354
+ ```typescript
355
+ import { writeFile } from "node:fs/promises";
356
+ import { app } from "electron";
357
+ import path from "node:path";
358
+
359
+ ipcMain.handle("save-webcam-photo", async (_event, photoBuffer: Buffer) => {
360
+ const userDataPath = path.join(app.getPath("userData"), "user-photos");
361
+ await writeFile(path.join(userDataPath, "selfie.jpg"), photoBuffer);
362
+ });
363
+ ```
364
+
365
+ The cache is read-only for presentation assets. No write operations are exposed to the renderer. Use standard Node.js file APIs for user-generated content like webcam photos or uploads.
366
+
367
+ Source: Maintainer interview
368
+
369
+ ### HIGH Tension: Dev passthrough simplicity vs production correctness
370
+
371
+ `devPassthrough` makes development fast but changes behavior: downloads are skipped, `onSyncFailure` is overridden to `"throw"`, URLs are remote. Code working in dev may break in production offline mode — especially authenticated downloads and sync failure resilience.
372
+
373
+ See also: cache-configuration/SKILL.md § Common Mistakes
374
+
375
+ ### HIGH Tension: Sync resilience vs stale content
376
+
377
+ `"serve-last-snapshot"` never shows a blank screen but may serve outdated content indefinitely if syncs keep failing. `"throw"` is honest but leaves the UI empty. Choose based on whether stale content or no content is worse for your kiosk use case.
378
+
379
+ See also: cache-configuration/SKILL.md § Common Mistakes
380
+
381
+ ## Pre-Deploy Summary
382
+
383
+ - [ ] `devPassthrough: false` set explicitly
384
+ - [ ] `onSyncFailure` mode chosen deliberately
385
+ - [ ] `app.requestSingleInstanceLock()` in place
386
+ - [ ] `maxCacheBytes` set for device; `reserveFreeBytes` tuned for hardware or default accepted
387
+ - [ ] `storagePath` writable on target platform
388
+ - [ ] `logging.onLog` wired to production log sink
389
+ - [ ] Package used only for read-only presentation assets
390
+ - [ ] Offline mode tested with real storage path and media:// URLs
391
+ - [ ] Full catalog fits on target device disk
392
+
393
+ ---
394
+
395
+ See also: cache-configuration/SKILL.md — All createMediaCache options
396
+ See also: getting-started/SKILL.md — Initial wiring and setup
397
+ See also: authenticated-downloads/SKILL.md — Verify auth works in offline mode