@ultimat3/core 22.3.2 → 22.3.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/core",
3
- "version": "22.3.2",
3
+ "version": "22.3.3",
4
4
  "description": "Ultimate's foundation: errors, context, env, config, clock, ids, logging, telemetry, lifecycle",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,6 +40,6 @@
40
40
  "test": "bun test"
41
41
  },
42
42
  "dependencies": {
43
- "@ultimat3/schema": "22.3.2"
43
+ "@ultimat3/schema": "22.3.3"
44
44
  }
45
45
  }
package/src/config-pwa.ts CHANGED
@@ -37,6 +37,12 @@ export interface PwaOfflineConfig {
37
37
  readonly image: string | null;
38
38
  readonly font: string | null;
39
39
  readonly neverCache: readonly string[];
40
+ /**
41
+ * A page rendered for someone (a policy, a `stream`, a `no-store`/`private` cache). `'never'`
42
+ * (default): not cached anywhere, its route `network-only`. `'last-member'`: kept offline for the
43
+ * most recent member only — declare it only with a sign-out that clears it (`clear-pages`).
44
+ */
45
+ readonly personalPages: 'never' | 'last-member';
40
46
  }
41
47
 
42
48
  export interface PwaConfig {
@@ -58,6 +64,52 @@ export interface PwaConfig {
58
64
  * worse than a boot that names the four values it needs.
59
65
  */
60
66
  readonly colors: PwaColors | undefined;
67
+ /**
68
+ * The manifest `id`: one installed app across every locale's manifest. Absent, the scope (`/`),
69
+ * which is what a browser computes from the default locale's `start_url` anyway.
70
+ */
71
+ readonly id?: string;
72
+ /** The install sheet's description. One string, or one per locale (`{ 'es-co': …, en: … }`). */
73
+ readonly description?: PwaText;
74
+ /** `categories`, e.g. `['business', 'productivity']` — W3C's list is a hint, not an enum. */
75
+ readonly categories?: readonly string[];
76
+ /** Home-screen shortcuts. `url` is the DEFAULT locale's path; each manifest spells it in its own. */
77
+ readonly shortcuts?: readonly PwaShortcut[];
78
+ /** Chrome's richer install sheet. `src` may differ per locale — a screenshot shows copy. */
79
+ readonly screenshots?: readonly PwaScreenshot[];
80
+ }
81
+
82
+ /**
83
+ * Manifest text, per locale where it is read: a string is every locale's, a record is keyed by
84
+ * locale tag and falls back to the default locale's entry. Each locale gets its own manifest
85
+ * (`/manifest.webmanifest`, `/en/manifest.webmanifest`), so each speaks its own language.
86
+ */
87
+ export type PwaText = string | Readonly<Record<string, string>>;
88
+
89
+ export interface PwaImage {
90
+ /** Absolute path, e.g. `/assets/shortcut-panel.png`. */
91
+ readonly src: string;
92
+ readonly sizes: string;
93
+ readonly type: string;
94
+ readonly purpose?: 'any' | 'maskable' | 'monochrome';
95
+ }
96
+
97
+ export interface PwaShortcut {
98
+ readonly name: PwaText;
99
+ readonly shortName?: PwaText;
100
+ readonly description?: PwaText;
101
+ /** The default locale's absolute path (`/panel`); the `en` manifest names `/en/panel`. */
102
+ readonly url: string;
103
+ readonly icons?: readonly PwaImage[];
104
+ }
105
+
106
+ export interface PwaScreenshot {
107
+ /** Absolute path, or one per locale. */
108
+ readonly src: PwaText;
109
+ readonly sizes: string;
110
+ readonly type: string;
111
+ readonly formFactor?: 'narrow' | 'wide';
112
+ readonly label?: PwaText;
61
113
  }
62
114
 
63
115
  /**
@@ -127,6 +179,12 @@ export function pwaIssues(pwa: PwaConfig, issues: string[]): boolean {
127
179
  `pwa.offline.fallback is required when pwa.enabled is true and must be an absolute route path like "/offline", and is ${describeValue(fallback)}`,
128
180
  );
129
181
  }
182
+ const personal: unknown = pwa.offline?.personalPages;
183
+ if (personal !== undefined && personal !== 'never' && personal !== 'last-member') {
184
+ issues.push(
185
+ `pwa.offline.personalPages must be 'never' or 'last-member', and is ${describeValue(personal)}`,
186
+ );
187
+ }
130
188
  // `typeof !== 'object' || null`, never `=== undefined`: an untyped `app.config.ts` writing
131
189
  // `pwa.colors: null` reached `null[scheme]` one line down and took the boot out with a native
132
190
  // `TypeError`, from the validator whose whole job is producing an instruction instead of one.
@@ -145,5 +203,33 @@ export function pwaIssues(pwa: PwaConfig, issues: string[]): boolean {
145
203
  }
146
204
  }
147
205
  }
206
+ manifestIssues(pwa, issues);
148
207
  return issues.length > before;
149
208
  }
209
+
210
+ /** A path the manifest names must be absolute: a relative one resolves against the manifest's URL. */
211
+ const absolute = (value: unknown): boolean => typeof value === 'string' && value.startsWith('/');
212
+
213
+ const texts = (value: unknown): readonly unknown[] =>
214
+ value !== null && typeof value === 'object' ? Object.values(value) : [value];
215
+
216
+ /** The optional manifest members, screened for the one mistake a browser drops them for silently. */
217
+ function manifestIssues(pwa: PwaConfig, issues: string[]): void {
218
+ if (pwa.id !== undefined && !absolute(pwa.id)) {
219
+ issues.push(`pwa.id must be an absolute path like "/", and is ${describeValue(pwa.id)}`);
220
+ }
221
+ for (const [i, shortcut] of (pwa.shortcuts ?? []).entries()) {
222
+ if (!absolute(shortcut?.url)) {
223
+ issues.push(
224
+ `pwa.shortcuts[${i}].url must be an absolute path, and is ${describeValue(shortcut?.url)}`,
225
+ );
226
+ }
227
+ }
228
+ for (const [i, screenshot] of (pwa.screenshots ?? []).entries()) {
229
+ if (!texts(screenshot?.src).every(absolute)) {
230
+ issues.push(
231
+ `pwa.screenshots[${i}].src must be an absolute path (or one per locale), and is ${describeValue(screenshot?.src)}`,
232
+ );
233
+ }
234
+ }
235
+ }
package/src/config.ts CHANGED
@@ -285,7 +285,7 @@ function defaults(name: string): Omit<AppConfig, 'name' | 'site' | 'seo'> {
285
285
  auth: { signInPath: null },
286
286
  pwa: {
287
287
  enabled: false,
288
- offline: { fallback: null, image: null, font: null, neverCache: [] },
288
+ offline: { fallback: null, image: null, font: null, neverCache: [], personalPages: 'never' },
289
289
  backgroundSync: false,
290
290
  push: false,
291
291
  name: '',
package/src/index.ts CHANGED
@@ -109,7 +109,16 @@ export type {
109
109
  ThemeMode,
110
110
  } from './config';
111
111
  export { defineConfig, INBOX_RETENTION_KEYS } from './config';
112
- export type { PwaColors, PwaConfig, PwaOfflineConfig, PwaSchemeColors } from './config-pwa';
112
+ export type {
113
+ PwaColors,
114
+ PwaConfig,
115
+ PwaImage,
116
+ PwaOfflineConfig,
117
+ PwaSchemeColors,
118
+ PwaScreenshot,
119
+ PwaShortcut,
120
+ PwaText,
121
+ } from './config-pwa';
113
122
  export { PWA_COLOR_KEYS, PWA_SCHEMES } from './config-pwa';
114
123
  export type {
115
124
  SeoConfig,