@satinhq/kit 0.1.0-beta.3 → 0.1.0-beta.4
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/NOTICE +1 -1
- package/README.md +1 -2
- package/dist/acknowledgements/inventory.json +1 -1
- package/dist/actions/RowActions.d.ts +2 -2
- package/dist/actions/RowActions.js +91 -25
- package/dist/actions/fullSwipeContact.d.ts +73 -0
- package/dist/actions/fullSwipeContact.js +186 -0
- package/dist/actions/rowScrub.d.ts +7 -4
- package/dist/actions/rowScrub.js +39 -37
- package/dist/actions/types.d.ts +13 -7
- package/dist/app/InstallComponents.d.ts +4 -7
- package/dist/app/InstallComponents.js +7 -7
- package/dist/app/InstallationDiagnostics.d.ts +2 -1
- package/dist/app/InstallationDiagnostics.js +2 -1
- package/dist/app/appearance.d.ts +5 -3
- package/dist/app/appearance.js +5 -3
- package/dist/app/appearanceStore.d.ts +3 -2
- package/dist/app/buildInfo.d.ts +4 -4
- package/dist/app/buildInfo.js +2 -2
- package/dist/app/definition.d.ts +11 -3
- package/dist/app/definition.js +2 -0
- package/dist/app/head.d.ts +2 -1
- package/dist/app/head.js +13 -16
- package/dist/app/inlineCSP.d.ts +6 -0
- package/dist/app/inlineCSP.js +25 -0
- package/dist/app/install.d.ts +1 -0
- package/dist/app/install.js +5 -2
- package/dist/app/installCheck.js +32 -86
- package/dist/app/installContext.d.ts +10 -5
- package/dist/app/installContext.js +2 -0
- package/dist/app/installCustom.js +1 -0
- package/dist/app/installExperience.js +0 -60
- package/dist/app/installationFailure.d.ts +3 -2
- package/dist/app/installationFailure.js +1 -1
- package/dist/app/launchScreen.d.ts +6 -2
- package/dist/app/launchScreen.js +104 -1
- package/dist/app/launchScreenRuntime.d.ts +31 -4
- package/dist/app/launchScreenRuntime.js +150 -22
- package/dist/app/launchWatchdog.js +25 -65
- package/dist/app/platformScripts.js +11 -0
- package/dist/app/styles.d.ts +3 -0
- package/dist/app/styles.js +42 -0
- package/dist/app/viewportValue.d.ts +2 -1
- package/dist/app/vite.d.ts +4 -0
- package/dist/app/vite.js +22 -24
- package/dist/content/content.d.ts +35 -30
- package/dist/content/content.js +19 -16
- package/dist/content/layout.d.ts +10 -2
- package/dist/content/layout.js +4 -2
- package/dist/content/scaledMetric.d.ts +2 -1
- package/dist/content/scaledMetric.js +2 -1
- package/dist/controls/ColorPicker.d.ts +2 -4
- package/dist/controls/ColorPicker.js +2 -4
- package/dist/controls/DatePicker.d.ts +4 -4
- package/dist/controls/DatePicker.js +2 -2
- package/dist/controls/ProgressView.d.ts +4 -4
- package/dist/controls/ProgressView.js +1 -2
- package/dist/data/database.d.ts +9 -8
- package/dist/data/database.js +4 -4
- package/dist/docs/reference.md +982 -964
- package/dist/docs/tailwind.md +76 -0
- package/dist/docs/tokens.md +1 -1
- package/dist/gestures/GestureView.d.ts +3 -1
- package/dist/gestures/GestureView.js +2 -1
- package/dist/gestures/gesture.d.ts +22 -2
- package/dist/gestures/gesture.js +7 -2
- package/dist/glass/GlassEffect.d.ts +2 -1
- package/dist/glass/GlassEffect.js +2 -1
- package/dist/glass/GlassPresentation.d.ts +1 -0
- package/dist/glass/GlassPresentation.js +21 -4
- package/dist/glass/TabBar.js +23 -1
- package/dist/glass/errorAlert.js +2 -1
- package/dist/glass/errors.d.ts +3 -1
- package/dist/glass/fallback/runtime.d.ts +1 -0
- package/dist/glass/fallback/runtime.js +8 -1
- package/dist/glass/liquidContext.d.ts +2 -0
- package/dist/glass/menuPresenter.d.ts +2 -0
- package/dist/glass/regions.d.ts +2 -1
- package/dist/glass/renderSpecs.d.ts +1 -0
- package/dist/glass/renderSpecs.js +1 -0
- package/dist/glass/webgpu/controlRim.d.ts +29 -0
- package/dist/glass/webgpu/controlRim.js +68 -0
- package/dist/glass/webgpu/passes.d.ts +2 -2
- package/dist/glass/webgpu/production.d.ts +1 -0
- package/dist/glass/webgpu/production.js +27 -1
- package/dist/glass/webgpu/rasterUniforms.js +9 -0
- package/dist/glass/webgpu/shaders.d.ts +2 -2
- package/dist/glass/webgpu/shaders.js +5 -1
- package/dist/haptics/SensoryButton.d.ts +4 -4
- package/dist/haptics/SensoryButton.js +3 -3
- package/dist/image/Image.d.ts +6 -3
- package/dist/image/Image.js +4 -3
- package/dist/image/asset.d.ts +2 -1
- package/dist/keyboard/adapter.d.ts +3 -4
- package/dist/keyboard/geometry.d.ts +1 -1
- package/dist/list-form/ListForm.d.ts +8 -7
- package/dist/list-form/ListForm.js +2 -2
- package/dist/list-form/editing.d.ts +5 -6
- package/dist/list-form/editing.js +4 -5
- package/dist/list-form/offsets.d.ts +2 -2
- package/dist/list-form/offsets.js +2 -2
- package/dist/media/react.d.ts +2 -1
- package/dist/media/react.js +2 -1
- package/dist/media/types.d.ts +6 -3
- package/dist/motion/TransitionView.d.ts +4 -2
- package/dist/motion/TransitionView.js +3 -2
- package/dist/motion/animationValue.d.ts +1 -1
- package/dist/motion/matchedGeometry.d.ts +6 -5
- package/dist/motion/matchedGeometry.js +2 -1
- package/dist/motion/transition.d.ts +4 -1
- package/dist/motion/transition.js +2 -0
- package/dist/navigation/DetailList.d.ts +1 -1
- package/dist/navigation/DetailList.js +1 -1
- package/dist/navigation/Sidebar.d.ts +3 -1
- package/dist/navigation/Sidebar.js +1 -1
- package/dist/navigation/SidebarSymbol.d.ts +2 -1
- package/dist/navigation/SidebarSymbol.js +2 -1
- package/dist/navigation/navigationContext.d.ts +2 -1
- package/dist/navigation/navigationContext.js +2 -1
- package/dist/navigation/splitContext.d.ts +2 -1
- package/dist/navigation/splitContext.js +2 -1
- package/dist/navigation/toolbarMinimization.d.ts +4 -3
- package/dist/navigation/types.d.ts +7 -5
- package/dist/presentation/Presentation.d.ts +8 -7
- package/dist/presentation/Presentation.js +3 -2
- package/dist/presentation/model.d.ts +2 -1
- package/dist/press/Pressable.d.ts +5 -1
- package/dist/press/Pressable.js +2 -0
- package/dist/press/stateMachine.d.ts +1 -1
- package/dist/public/Label.d.ts +2 -1
- package/dist/public/Label.js +2 -1
- package/dist/public/Screen.d.ts +4 -6
- package/dist/public/actions.d.ts +2 -1
- package/dist/public/actions.js +2 -1
- package/dist/public/buttonBorderShape.d.ts +2 -1
- package/dist/public/colors.d.ts +7 -4
- package/dist/public/colors.js +3 -2
- package/dist/public/controlStyle.d.ts +17 -14
- package/dist/public/controls.d.ts +27 -26
- package/dist/public/controls.js +20 -19
- package/dist/public/deviceActions.d.ts +6 -4
- package/dist/public/deviceActions.js +4 -2
- package/dist/public/environment.d.ts +16 -13
- package/dist/public/environment.js +11 -9
- package/dist/public/filePickers.d.ts +6 -4
- package/dist/public/filePickers.js +3 -2
- package/dist/public/glass.d.ts +6 -3
- package/dist/public/glass.js +2 -1
- package/dist/public/glassValue.d.ts +3 -4
- package/dist/public/glassValue.js +3 -4
- package/dist/public/keyboard.d.ts +4 -1
- package/dist/public/keyboard.js +2 -1
- package/dist/public/navigationTransition.d.ts +5 -5
- package/dist/public/navigationTransition.js +2 -2
- package/dist/public/presentation.d.ts +8 -7
- package/dist/public/presentation.js +5 -5
- package/dist/public/runtime.d.ts +9 -8
- package/dist/public/runtime.js +7 -7
- package/dist/public/scroll.d.ts +6 -3
- package/dist/public/scroll.js +3 -1
- package/dist/public/structure.d.ts +2 -2
- package/dist/public/toolbar.d.ts +15 -11
- package/dist/public/toolbar.js +12 -7
- package/dist/reachability/model.d.ts +4 -3
- package/dist/reachability/react.d.ts +4 -1
- package/dist/reachability/react.js +2 -1
- package/dist/root/SatinRoot.d.ts +3 -3
- package/dist/root/SatinRoot.js +12 -36
- package/dist/root/mountSatin.d.ts +1 -1
- package/dist/root/mountSatin.js +26 -35
- package/dist/root/runtimeContext.d.ts +2 -1
- package/dist/root/runtimeContext.js +2 -1
- package/dist/safe-area/Overlay.d.ts +3 -2
- package/dist/safe-area/Overlay.js +2 -1
- package/dist/safe-area/SafeAreaInset.d.ts +11 -7
- package/dist/safe-area/SafeAreaInset.js +7 -6
- package/dist/safe-area/context.d.ts +3 -3
- package/dist/safe-area/context.js +1 -1
- package/dist/safe-area/displayCorners.d.ts +4 -2
- package/dist/safe-area/displayCorners.js +4 -2
- package/dist/safe-area/modifiers.d.ts +14 -10
- package/dist/safe-area/modifiers.js +8 -4
- package/dist/scroll/ScrollSurface.js +5 -1
- package/dist/scroll/ScrollTarget.d.ts +1 -1
- package/dist/scroll/ScrollTarget.js +1 -1
- package/dist/scroll/edgePolicy.d.ts +2 -2
- package/dist/scroll/position.d.ts +5 -3
- package/dist/scroll/types.d.ts +1 -1
- package/dist/search/PadSearchDrawer.d.ts +7 -0
- package/dist/search/PadSearchDrawer.js +8 -0
- package/dist/search/PadSearchPresentationContext.d.ts +6 -0
- package/dist/search/PadSearchPresentationContext.js +4 -0
- package/dist/search/SearchDrawer.d.ts +1 -1
- package/dist/search/SearchDrawer.js +6 -1
- package/dist/search/SearchSuggestionsPopover.d.ts +6 -0
- package/dist/search/SearchSuggestionsPopover.js +42 -0
- package/dist/search/SearchToolbarField.d.ts +2 -1
- package/dist/search/SearchToolbarField.js +28 -7
- package/dist/search/padSearchBarMotion.d.ts +69 -0
- package/dist/search/padSearchBarMotion.js +73 -0
- package/dist/search/padSearchFieldSamples.json +491 -0
- package/dist/search/searchPlacement.d.ts +3 -0
- package/dist/search/searchPlacement.js +7 -0
- package/dist/search/searchable.d.ts +14 -12
- package/dist/search/usePadSearchFieldMotion.d.ts +4 -0
- package/dist/search/usePadSearchFieldMotion.js +100 -0
- package/dist/search/useSearchPresentation.d.ts +2 -1
- package/dist/search/useSearchPresentation.js +23 -5
- package/dist/server.d.ts +4 -4
- package/dist/server.js +4 -4
- package/dist/shapes/corners.d.ts +3 -3
- package/dist/shapes/corners.js +1 -2
- package/dist/styles.css +8289 -8244
- package/dist/sw/vite.d.ts +4 -0
- package/dist/sw/vite.js +2 -0
- package/dist/symbols/Symbol.d.ts +7 -5
- package/dist/symbols/Symbol.js +5 -3
- package/dist/symbols/catalogue.d.ts +1 -1
- package/dist/symbols/types.d.ts +6 -4
- package/dist/tabs/TabSection.d.ts +3 -2
- package/dist/tabs/TabSection.js +2 -1
- package/dist/tabs/TabView.js +54 -51
- package/dist/tasks/useTask.d.ts +5 -4
- package/dist/tasks/useTask.js +4 -3
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +3 -2
- package/dist/text-input/focusChain.d.ts +4 -3
- package/dist/text-input/focusChain.js +3 -2
- package/dist/text-input/kinds.d.ts +1 -1
- package/dist/web/WebView.d.ts +10 -3
- package/dist/web/WebView.js +3 -2
- package/dist/web/authentication.d.ts +12 -1
- package/dist/web/authentication.js +2 -1
- package/dist/web/openURL.d.ts +10 -4
- package/dist/web/openURL.js +4 -2
- package/dist/web/useWebAuthentication.d.ts +2 -1
- package/dist/web/useWebAuthentication.js +2 -1
- package/dist/zoom/Zoomable.d.ts +3 -3
- package/dist/zoom/Zoomable.js +2 -2
- package/package.json +1 -1
|
@@ -1,9 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
// controller as ordinary readiness. Keep this function self-contained.
|
|
3
|
-
export function installLaunchScreenRuntime() {
|
|
1
|
+
export function installLaunchScreenRuntime(options) {
|
|
4
2
|
const launchWindow = window;
|
|
5
|
-
if (launchWindow.
|
|
6
|
-
return launchWindow.
|
|
3
|
+
if (launchWindow.__SATIN_PRELAUNCH__)
|
|
4
|
+
return launchWindow.__SATIN_PRELAUNCH__;
|
|
5
|
+
const host = document.createElement('satin-prelaunch');
|
|
6
|
+
const root = host.attachShadow({ mode: 'closed' });
|
|
7
|
+
const style = document.createElement('style');
|
|
8
|
+
style.nonce = options.nonce ?? document.currentScript?.nonce ?? '';
|
|
9
|
+
style.textContent = options.css;
|
|
10
|
+
const content = document.createElement('div');
|
|
11
|
+
content.innerHTML = options.markup;
|
|
12
|
+
root.append(style, content);
|
|
13
|
+
document.documentElement.append(host);
|
|
14
|
+
const element = (id) => root.getElementById(id);
|
|
15
|
+
const cover = element('satin-launch-screen');
|
|
16
|
+
const failure = document.createElement('section');
|
|
17
|
+
failure.id = 'satin-launch-error';
|
|
18
|
+
failure.setAttribute('role', 'alert');
|
|
19
|
+
failure.hidden = true;
|
|
20
|
+
let state = options.cover === false ? 'idle' : 'preparing';
|
|
21
|
+
cover.hidden = options.cover === false;
|
|
22
|
+
const dark = matchMedia('(prefers-color-scheme: dark)');
|
|
23
|
+
const appearance = (background, ink) => {
|
|
24
|
+
host.style.setProperty('--satin-launch-background', background, 'important');
|
|
25
|
+
host.style.setProperty('--satin-launch-ink', ink, 'important');
|
|
26
|
+
};
|
|
27
|
+
const systemAppearance = () => appearance(dark.matches ? options.darkBackground : options.background, dark.matches ? options.darkInk : '#1c1c1e');
|
|
28
|
+
systemAppearance();
|
|
29
|
+
dark.addEventListener?.('change', systemAppearance);
|
|
7
30
|
let value = 0;
|
|
8
31
|
let velocity = 0;
|
|
9
32
|
let target = 0;
|
|
@@ -21,7 +44,7 @@ export function installLaunchScreenRuntime() {
|
|
|
21
44
|
// Extend the cover into that excluded space so its center stays in the
|
|
22
45
|
// native startup image's screen frame. Windowed apps keep their own frame.
|
|
23
46
|
const excludedTop = standalone && fullWidth ? Math.max(0, screen.height - innerHeight) : 0;
|
|
24
|
-
|
|
47
|
+
host.style.setProperty('--satin-launch-excluded-top', `${excludedTop}px`, 'important');
|
|
25
48
|
};
|
|
26
49
|
alignCover();
|
|
27
50
|
window.addEventListener('resize', alignCover);
|
|
@@ -33,7 +56,7 @@ export function installLaunchScreenRuntime() {
|
|
|
33
56
|
let retiring = null;
|
|
34
57
|
let appliedUpdate = null;
|
|
35
58
|
let updateAnimation = null;
|
|
36
|
-
const ringElement = () =>
|
|
59
|
+
const ringElement = () => element('satin-launch-progress');
|
|
37
60
|
const paint = () => {
|
|
38
61
|
ringElement()
|
|
39
62
|
?.querySelector('circle')
|
|
@@ -101,7 +124,7 @@ export function installLaunchScreenRuntime() {
|
|
|
101
124
|
.finished.catch(() => { });
|
|
102
125
|
};
|
|
103
126
|
const retire = (onRetired) => {
|
|
104
|
-
const screen =
|
|
127
|
+
const screen = element('satin-launch-screen');
|
|
105
128
|
if (!screen) {
|
|
106
129
|
onRetired();
|
|
107
130
|
return;
|
|
@@ -110,7 +133,7 @@ export function installLaunchScreenRuntime() {
|
|
|
110
133
|
retiring ??= (async () => {
|
|
111
134
|
const ring = ringElement();
|
|
112
135
|
if (ring && !ring.hasAttribute('hidden')) {
|
|
113
|
-
//
|
|
136
|
+
// The failure panel stays behind the completing arc.
|
|
114
137
|
screen.style.zIndex = '20003';
|
|
115
138
|
ring.setAttribute('aria-label', 'Finishing launch');
|
|
116
139
|
await complete();
|
|
@@ -135,6 +158,100 @@ export function installLaunchScreenRuntime() {
|
|
|
135
158
|
});
|
|
136
159
|
};
|
|
137
160
|
const controller = {
|
|
161
|
+
version: 1,
|
|
162
|
+
stylesReady() {
|
|
163
|
+
return Promise.all([...document.querySelectorAll('link[data-satin-style]')].map(link => {
|
|
164
|
+
if (link.dataset.satinStyleReady === 'true')
|
|
165
|
+
return Promise.resolve();
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
const cleanup = () => {
|
|
168
|
+
link.removeEventListener('load', loaded);
|
|
169
|
+
link.removeEventListener('error', failed);
|
|
170
|
+
};
|
|
171
|
+
const loaded = () => {
|
|
172
|
+
cleanup();
|
|
173
|
+
resolve();
|
|
174
|
+
};
|
|
175
|
+
const failed = () => {
|
|
176
|
+
cleanup();
|
|
177
|
+
reject(new Error('The app stylesheet failed to load. Please retry.'));
|
|
178
|
+
};
|
|
179
|
+
if (link.dataset.satinStyleError === 'true') {
|
|
180
|
+
failed();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
link.addEventListener('load', loaded, { once: true });
|
|
184
|
+
link.addEventListener('error', failed, { once: true });
|
|
185
|
+
});
|
|
186
|
+
})).then(() => { });
|
|
187
|
+
},
|
|
188
|
+
appearance,
|
|
189
|
+
snapshot() {
|
|
190
|
+
const ring = ringElement();
|
|
191
|
+
const box = ring.getBoundingClientRect();
|
|
192
|
+
const style = getComputedStyle(ring);
|
|
193
|
+
return {
|
|
194
|
+
version: 1,
|
|
195
|
+
state,
|
|
196
|
+
error: failure.isConnected ? (failure.textContent ?? '') : '',
|
|
197
|
+
coverVisible: !cover.hidden,
|
|
198
|
+
ringVisible: !cover.hidden &&
|
|
199
|
+
!ring.hasAttribute('hidden') &&
|
|
200
|
+
style.display !== 'none' &&
|
|
201
|
+
Number(style.opacity) > 0,
|
|
202
|
+
label: ring.getAttribute('aria-label'),
|
|
203
|
+
offset: getComputedStyle(ring.querySelector('circle')).strokeDashoffset,
|
|
204
|
+
box: [box.x, box.y, box.width, box.height],
|
|
205
|
+
background: getComputedStyle(cover).backgroundColor,
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
error(reason, title = 'Unable to Open App', retry, detail) {
|
|
209
|
+
state = 'failed';
|
|
210
|
+
failure.replaceChildren();
|
|
211
|
+
const heading = document.createElement('h1');
|
|
212
|
+
heading.textContent = title;
|
|
213
|
+
const message = document.createElement('p');
|
|
214
|
+
message.textContent = reason;
|
|
215
|
+
const diagnostics = document.createElement('p');
|
|
216
|
+
diagnostics.className = 'detail';
|
|
217
|
+
diagnostics.textContent = detail ?? '';
|
|
218
|
+
diagnostics.hidden = !detail;
|
|
219
|
+
const button = document.createElement('button');
|
|
220
|
+
button.type = 'button';
|
|
221
|
+
button.textContent = 'Retry';
|
|
222
|
+
button.onclick =
|
|
223
|
+
retry ??
|
|
224
|
+
(() => {
|
|
225
|
+
try {
|
|
226
|
+
sessionStorage.setItem('satin-install-reset:' + options.scope, '1');
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
/* Retry still reloads without storage. */
|
|
230
|
+
}
|
|
231
|
+
location.reload();
|
|
232
|
+
});
|
|
233
|
+
failure.append(heading, message, diagnostics, button);
|
|
234
|
+
root.append(failure);
|
|
235
|
+
failure.hidden = false;
|
|
236
|
+
// The same controller finishes a visible arc before showing its failure.
|
|
237
|
+
if (ringElement().hasAttribute('hidden'))
|
|
238
|
+
cover.hidden = true;
|
|
239
|
+
else
|
|
240
|
+
controller.fail(() => {
|
|
241
|
+
if (state === 'failed')
|
|
242
|
+
cover.hidden = true;
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
recover() {
|
|
246
|
+
failure.remove();
|
|
247
|
+
if (state === 'failed' && !revealed) {
|
|
248
|
+
state = options.cover === false ? 'idle' : 'preparing';
|
|
249
|
+
if (options.cover === false)
|
|
250
|
+
cover.hidden = true;
|
|
251
|
+
else
|
|
252
|
+
controller.restore();
|
|
253
|
+
}
|
|
254
|
+
},
|
|
138
255
|
updated(start = 0) {
|
|
139
256
|
if (appliedUpdate)
|
|
140
257
|
return appliedUpdate;
|
|
@@ -179,7 +296,7 @@ export function installLaunchScreenRuntime() {
|
|
|
179
296
|
const ring = ringElement();
|
|
180
297
|
// A phase ending does not mean the cover is ready to retire. Keep the
|
|
181
298
|
// real last sample until readiness or an explicit error owns the handoff.
|
|
182
|
-
if (
|
|
299
|
+
if (revealed || retiring || appliedUpdate || !progress || progress.total <= 0)
|
|
183
300
|
return;
|
|
184
301
|
if (!Number.isFinite(progress.loaded) || !Number.isFinite(progress.total))
|
|
185
302
|
return;
|
|
@@ -194,9 +311,11 @@ export function installLaunchScreenRuntime() {
|
|
|
194
311
|
},
|
|
195
312
|
complete,
|
|
196
313
|
reveal() {
|
|
197
|
-
if (revealed)
|
|
314
|
+
if (revealed || state === 'failed')
|
|
198
315
|
return;
|
|
199
316
|
revealed = true;
|
|
317
|
+
state = 'ready';
|
|
318
|
+
failure.remove();
|
|
200
319
|
window.dispatchEvent(new Event('satin-launch-revealed'));
|
|
201
320
|
retire(() => { });
|
|
202
321
|
},
|
|
@@ -218,7 +337,7 @@ export function installLaunchScreenRuntime() {
|
|
|
218
337
|
// spring, rather than leaving a cancelled completion targeted at 100.
|
|
219
338
|
target = downloadTarget;
|
|
220
339
|
const ring = ringElement();
|
|
221
|
-
const cover =
|
|
340
|
+
const cover = element('satin-launch-screen');
|
|
222
341
|
if (ring && !ring.hasAttribute('hidden'))
|
|
223
342
|
start();
|
|
224
343
|
for (const [element, duration] of [
|
|
@@ -227,7 +346,7 @@ export function installLaunchScreenRuntime() {
|
|
|
227
346
|
]) {
|
|
228
347
|
if (!element)
|
|
229
348
|
continue;
|
|
230
|
-
const opacity = Number(getComputedStyle(element).opacity);
|
|
349
|
+
const opacity = Number.parseFloat(getComputedStyle(element).opacity);
|
|
231
350
|
element.getAnimations().forEach(animation => animation.cancel());
|
|
232
351
|
if (Number.isFinite(opacity) && opacity < 1 && !element.hidden) {
|
|
233
352
|
void element
|
|
@@ -239,38 +358,47 @@ export function installLaunchScreenRuntime() {
|
|
|
239
358
|
.finished.catch(() => { });
|
|
240
359
|
}
|
|
241
360
|
}
|
|
242
|
-
const screen =
|
|
361
|
+
const screen = element('satin-launch-screen');
|
|
243
362
|
if (screen) {
|
|
244
363
|
screen.hidden = false;
|
|
245
364
|
screen.style.removeProperty('z-index');
|
|
246
365
|
}
|
|
247
366
|
},
|
|
248
367
|
};
|
|
249
|
-
launchWindow.
|
|
368
|
+
launchWindow.__SATIN_PRELAUNCH__ = controller;
|
|
250
369
|
return controller;
|
|
251
370
|
}
|
|
371
|
+
export function prelaunchController() {
|
|
372
|
+
const controller = window.__SATIN_PRELAUNCH__;
|
|
373
|
+
if (!controller || controller.version !== 1)
|
|
374
|
+
throw new Error('Satin pre-launch v1 bootstrap is missing.');
|
|
375
|
+
return controller;
|
|
376
|
+
}
|
|
377
|
+
export function showLaunchFailure(reason, title) {
|
|
378
|
+
prelaunchController().error(reason, title);
|
|
379
|
+
}
|
|
252
380
|
export function setLaunchScreen(visible) {
|
|
253
381
|
if (visible) {
|
|
254
|
-
|
|
382
|
+
prelaunchController().restore();
|
|
255
383
|
return;
|
|
256
384
|
}
|
|
257
|
-
|
|
385
|
+
prelaunchController().reveal();
|
|
258
386
|
}
|
|
259
387
|
export function setLaunchProgress(progress) {
|
|
260
388
|
window.dispatchEvent(new CustomEvent('satin-launch-progress', { detail: progress }));
|
|
261
|
-
|
|
389
|
+
prelaunchController().progress(progress);
|
|
262
390
|
}
|
|
263
391
|
/** Complete a visible arc before a cold-update document replacement. */
|
|
264
392
|
export function completeLaunchProgress() {
|
|
265
393
|
if (typeof document === 'undefined' || typeof document.getElementById !== 'function')
|
|
266
394
|
return Promise.resolve(true);
|
|
267
|
-
return
|
|
395
|
+
return prelaunchController().complete();
|
|
268
396
|
}
|
|
269
397
|
/** Confirm an applied update on its successor document before revealing the app. */
|
|
270
398
|
export function showAppliedUpdate() {
|
|
271
|
-
return
|
|
399
|
+
return prelaunchController().updated();
|
|
272
400
|
}
|
|
273
|
-
/**
|
|
401
|
+
/** Runs immediately after the inline environment, before application modules. */
|
|
274
402
|
export function resumeAppliedUpdate(scope, buildId) {
|
|
275
403
|
let ringStart = null;
|
|
276
404
|
try {
|
|
@@ -309,5 +437,5 @@ export function resumeAppliedUpdate(scope, buildId) {
|
|
|
309
437
|
}, { once: true });
|
|
310
438
|
}
|
|
311
439
|
if (ringStart !== null)
|
|
312
|
-
void window.
|
|
440
|
+
void window.__SATIN_PRELAUNCH__?.updated(ringStart);
|
|
313
441
|
}
|
|
@@ -16,63 +16,34 @@ export function installLaunchWatchdog(budgetMs) {
|
|
|
16
16
|
.filter(mark => mark.name.startsWith('satin-'));
|
|
17
17
|
const stage = marks.at(-1)?.name ?? 'satin-document-head';
|
|
18
18
|
console.error('[Satin launch]', reason, 'Last stage:', stage, recordedError);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
screen.setAttribute('role', 'alert');
|
|
22
|
-
screen.style.cssText =
|
|
23
|
-
'position:fixed;inset:0;z-index:20002;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px;box-sizing:border-box;background:var(--satin-launch-background,#f2f2f7);color:var(--satin-launch-ink,#1c1c1e);font:17px/1.45 system-ui;text-align:center;pointer-events:auto';
|
|
24
|
-
const title = document.createElement('h1');
|
|
25
|
-
title.textContent = 'Unable to Open App';
|
|
26
|
-
title.style.cssText = 'font-size:24px;margin:0 0 12px';
|
|
27
|
-
const message = document.createElement('p');
|
|
28
|
-
message.textContent = reason;
|
|
29
|
-
message.style.maxWidth = '420px';
|
|
30
|
-
const reached = document.createElement('p');
|
|
31
|
-
reached.textContent = 'Last stage: ' + stage;
|
|
32
|
-
reached.style.cssText = 'font-size:13px;opacity:.65;overflow-wrap:anywhere';
|
|
33
|
-
const retry = document.createElement('button');
|
|
34
|
-
retry.textContent = 'Retry';
|
|
35
|
-
retry.style.cssText =
|
|
36
|
-
'font:600 17px system-ui;color:#007aff;background:transparent;border:0;padding:16px 32px;min-height:44px';
|
|
37
|
-
retry.onclick = () => {
|
|
38
|
-
const definition = window
|
|
39
|
-
.__SATIN_DEFINITION__;
|
|
40
|
-
try {
|
|
41
|
-
sessionStorage.setItem('satin-install-reset:' + (definition?.scope ?? '/'), '1');
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
// A reload can still join the worker after its own deadline.
|
|
45
|
-
}
|
|
46
|
-
location.reload();
|
|
47
|
-
};
|
|
48
|
-
screen.append(title, message, reached, retry);
|
|
49
|
-
(document.body ?? document.documentElement).append(screen);
|
|
50
|
-
if (!document.body) {
|
|
51
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
52
|
-
if (screen.isConnected)
|
|
53
|
-
document.body?.append(screen);
|
|
54
|
-
}, { once: true });
|
|
55
|
-
}
|
|
56
|
-
const cover = document.getElementById('satin-launch-screen');
|
|
57
|
-
const controller = window.__SATIN_LAUNCH_SCREEN__;
|
|
58
|
-
const ring = document.getElementById('satin-launch-progress');
|
|
59
|
-
if (cover && ring && !ring.hasAttribute('hidden') && controller) {
|
|
60
|
-
// Put the failure underneath the cover until its visible arc completes.
|
|
61
|
-
screen.style.zIndex = '19999';
|
|
62
|
-
controller.fail(() => {
|
|
63
|
-
if (failed)
|
|
64
|
-
screen.style.zIndex = '20002';
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
else if (cover)
|
|
68
|
-
cover.hidden = true;
|
|
19
|
+
const controller = window.__SATIN_PRELAUNCH__;
|
|
20
|
+
controller?.error(reason, 'Unable to Open App', undefined, 'Last stage: ' + stage);
|
|
69
21
|
performance.mark('satin-launch-failed');
|
|
70
22
|
};
|
|
71
23
|
window.addEventListener('error', event => {
|
|
72
|
-
|
|
73
|
-
|
|
24
|
+
const target = event.target;
|
|
25
|
+
if (target instanceof HTMLLinkElement && target.rel === 'stylesheet') {
|
|
26
|
+
target.dataset.satinStyleError = 'true';
|
|
27
|
+
showFailure('The app stylesheet failed to load. Please retry.');
|
|
28
|
+
}
|
|
29
|
+
else if (target instanceof HTMLScriptElement) {
|
|
30
|
+
showFailure('The application script failed to load. Please retry.');
|
|
31
|
+
}
|
|
32
|
+
else if (event instanceof ErrorEvent) {
|
|
33
|
+
recordedError = event.message || 'Application script failed to load.';
|
|
34
|
+
showFailure('Launch failed. ' + recordedError.split('\n')[0].slice(0, 160));
|
|
35
|
+
}
|
|
36
|
+
}, true);
|
|
37
|
+
document.addEventListener('load', event => {
|
|
38
|
+
const link = event.target;
|
|
39
|
+
if (link instanceof HTMLLinkElement && link.hasAttribute('data-satin-style')) {
|
|
40
|
+
link.media = link.getAttribute('data-satin-style') || 'all';
|
|
41
|
+
link.dataset.satinStyleReady = 'true';
|
|
42
|
+
}
|
|
43
|
+
}, true);
|
|
74
44
|
window.addEventListener('unhandledrejection', event => {
|
|
75
45
|
recordedError = event.reason instanceof Error ? event.reason.message : String(event.reason);
|
|
46
|
+
showFailure('Launch failed. ' + recordedError.split('\n')[0].slice(0, 160));
|
|
76
47
|
});
|
|
77
48
|
window.addEventListener('satin-glass-error', event => {
|
|
78
49
|
const error = event.detail;
|
|
@@ -80,19 +51,8 @@ export function installLaunchWatchdog(budgetMs) {
|
|
|
80
51
|
showFailure('Glass could not start: ' + error.code + '.');
|
|
81
52
|
});
|
|
82
53
|
const recover = () => {
|
|
83
|
-
const restoreCover = failed;
|
|
84
54
|
failed = false;
|
|
85
|
-
|
|
86
|
-
const cover = document.getElementById('satin-launch-screen');
|
|
87
|
-
// The runtime owns normal ring settlement and cover retirement. Only undo
|
|
88
|
-
// the cover hiding performed by this watchdog's own failure screen.
|
|
89
|
-
if (cover && restoreCover) {
|
|
90
|
-
const controller = window.__SATIN_LAUNCH_SCREEN__;
|
|
91
|
-
if (controller)
|
|
92
|
-
controller.restore();
|
|
93
|
-
else
|
|
94
|
-
cover.hidden = false;
|
|
95
|
-
}
|
|
55
|
+
window.__SATIN_PRELAUNCH__?.recover();
|
|
96
56
|
};
|
|
97
57
|
const receiveProgress = (progress) => {
|
|
98
58
|
if (progress && (progress.loaded > lastBytes || (progress.activity ?? 0) > lastActivity)) {
|
|
@@ -178,7 +138,7 @@ export function installLaunchWatchdog(budgetMs) {
|
|
|
178
138
|
// A first install can download before the application module is available.
|
|
179
139
|
// Controlled documents are cached launches/updates and keep their existing policy.
|
|
180
140
|
if (!navigator.serviceWorker?.controller && !revealed)
|
|
181
|
-
window.
|
|
141
|
+
window.__SATIN_PRELAUNCH__?.progress(progress);
|
|
182
142
|
}
|
|
183
143
|
});
|
|
184
144
|
// A client's worker messages are queued until DOMContentLoaded, and a bootstrap module that
|
|
@@ -32,6 +32,17 @@ export function gateAppScripts(html) {
|
|
|
32
32
|
html.slice(firstScript.index, gate.index) +
|
|
33
33
|
html.slice(gate.index + gate[0].length);
|
|
34
34
|
}
|
|
35
|
+
// Fetch app styles without blocking the independent head environment. The
|
|
36
|
+
// head's capture listener activates each stylesheet only after it loads.
|
|
37
|
+
html = html.replace(/<link\b[^>]*>/gi, tag => {
|
|
38
|
+
const attributes = scriptAttributes(tag.slice(5, -1));
|
|
39
|
+
if (attributes.get('rel')?.value !== 'stylesheet')
|
|
40
|
+
return tag;
|
|
41
|
+
const media = attributes.get('media')?.value ?? 'all';
|
|
42
|
+
return tag
|
|
43
|
+
.replace(/\smedia\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/i, '')
|
|
44
|
+
.replace(/>$/, ` media="not all" data-satin-style="${media.replaceAll('"', '"')}">`);
|
|
45
|
+
});
|
|
35
46
|
return html.replace(scriptPattern, (original, attributes) => {
|
|
36
47
|
if (attributes === undefined)
|
|
37
48
|
return original;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { normalizePath } from 'vite';
|
|
3
|
+
const stylesheet = normalizePath(fileURLToPath(new URL('../styles.css', import.meta.url)));
|
|
4
|
+
const legacyImport = '\0satin:included-styles';
|
|
5
|
+
const stylesheetUrl = '/@satin/framework.css';
|
|
6
|
+
export function frameworkStylesheet(base) {
|
|
7
|
+
// Keep this a real stylesheet link: WebKit must load it before first paint.
|
|
8
|
+
// Vite processes the link during builds and precaches the resulting CSS asset.
|
|
9
|
+
return `<link rel="stylesheet" href="${base}${stylesheetUrl.slice(1)}">`;
|
|
10
|
+
}
|
|
11
|
+
export function includedStyles() {
|
|
12
|
+
let serving = false;
|
|
13
|
+
let base = '/';
|
|
14
|
+
return {
|
|
15
|
+
name: 'satin:included-styles',
|
|
16
|
+
enforce: 'pre',
|
|
17
|
+
configResolved(config) {
|
|
18
|
+
serving = config.command === 'serve';
|
|
19
|
+
base = config.base;
|
|
20
|
+
},
|
|
21
|
+
resolveId(id, importer) {
|
|
22
|
+
// Resolve a URL rather than embedding a filesystem path in HTML. Vite's
|
|
23
|
+
// build resolver does not URL-decode package paths such as @scope or spaces.
|
|
24
|
+
const queryAt = id.indexOf('?');
|
|
25
|
+
const path = queryAt < 0 ? id : id.slice(0, queryAt);
|
|
26
|
+
if (path === stylesheetUrl || path === base + stylesheetUrl.slice(1))
|
|
27
|
+
return stylesheet + (queryAt < 0 ? '' : id.slice(queryAt));
|
|
28
|
+
// In production Vite deduplicates the link and import in its CSS graph.
|
|
29
|
+
// In dev a JS CSS import would add a second <style> after the app CSS.
|
|
30
|
+
// The link already owns loading and CSS HMR, so old side-effect imports
|
|
31
|
+
// are empty modules. Leave CSS @imports and explicit ?inline/?url uses alone.
|
|
32
|
+
if (!serving || !importer || /\.(?:css|scss|sass|less|styl)(?:\?|$)/.test(importer))
|
|
33
|
+
return;
|
|
34
|
+
if (id === '@satinhq/kit/styles.css' || normalizePath(id) === stylesheet)
|
|
35
|
+
return legacyImport;
|
|
36
|
+
},
|
|
37
|
+
load(id) {
|
|
38
|
+
if (id === legacyImport)
|
|
39
|
+
return 'export {}';
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** How the app fits the screen around the notch and home indicator. `adaptive` picks a fit per
|
|
2
|
+
* device before the app renders. */
|
|
2
3
|
export type ViewportFitPolicy = 'adaptive' | 'auto' | 'cover' | 'contain';
|
|
3
4
|
export declare const SATIN_VIEWPORT = "width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover";
|
|
4
5
|
/** Self-contained so the generated head and runtime use exactly the same rule. */
|
package/dist/app/vite.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
2
|
import { type AppDefinition } from './definition.js';
|
|
3
3
|
import { type AppFixture } from './head.js';
|
|
4
|
+
/** Build options for `satinApp`: a custom install page, strict glass, the development menu and a
|
|
5
|
+
* media probe. */
|
|
4
6
|
export interface SatinAppOptions {
|
|
5
7
|
/** Output-relative bundled probe for remote-only media. Default: first emitted media
|
|
6
8
|
* file. Proves this deployment server only, never the remote source or playback. */
|
|
@@ -22,6 +24,8 @@ export interface SatinAppOptions {
|
|
|
22
24
|
readonly installPage?: string;
|
|
23
25
|
}
|
|
24
26
|
export declare function platformBuildWarning(app: AppDefinition, production: boolean): string | undefined;
|
|
27
|
+
/** Generates the app shell and loads framework CSS once, before app styles.
|
|
28
|
+
* The stylesheet participates in Vite CSS HMR and builds; pre-launch paints independently. */
|
|
25
29
|
export declare function satinApp(app: AppDefinition, options?: SatinAppOptions): Plugin[];
|
|
26
30
|
/** Multi-install lab fixture only. Production apps use satinApp(app). */
|
|
27
31
|
export declare function satinAppFixtures(input: readonly AppFixture[], options?: SatinAppOptions): Plugin[];
|
package/dist/app/vite.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { inlinePolicySources } from './inlineCSP.js';
|
|
1
2
|
import { bundledLicenseNotices, renderNotices } from '../acknowledgements/bundledNotices.js';
|
|
2
3
|
import { serveStaticFile } from '../server.js';
|
|
3
4
|
import { mediaTypes } from './mediaTypes.js';
|
|
@@ -5,8 +6,7 @@ import { extname } from 'node:path';
|
|
|
5
6
|
import { imageAssets } from '../image/vite.js';
|
|
6
7
|
import { generateAppIcons, generateStartupImage } from './icons.js';
|
|
7
8
|
import { generateWebClipProfile } from './mdm.js';
|
|
8
|
-
import { launchScreenMarkup } from './launchScreen.js';
|
|
9
|
-
import { resumeAppliedUpdate } from './launchScreenRuntime.js';
|
|
9
|
+
import { launchScreenMarkup, prelaunchCSS } from './launchScreen.js';
|
|
10
10
|
import { startupImages } from './startup.js';
|
|
11
11
|
import { access, readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
12
12
|
import { installPage, installRouteForBase } from './install.js';
|
|
@@ -19,6 +19,7 @@ import { deploymentExpectation } from './deployment.js';
|
|
|
19
19
|
import { gateAppScripts } from './platformScripts.js';
|
|
20
20
|
import { devMenuBuild } from '../dev-menu/build.js';
|
|
21
21
|
import { createBuildInfo } from './buildInfoBuild.js';
|
|
22
|
+
import { frameworkStylesheet, includedStyles } from './styles.js';
|
|
22
23
|
export function platformBuildWarning(app, production) {
|
|
23
24
|
if (!production || app.platforms !== 'all')
|
|
24
25
|
return undefined;
|
|
@@ -26,6 +27,8 @@ export function platformBuildWarning(app, production) {
|
|
|
26
27
|
'desktop, Chromium and Gecko browsers to execute your app. ' +
|
|
27
28
|
"Satin only supports iPhone/iPad WebKit. Restore platforms: 'ios' before shipping.");
|
|
28
29
|
}
|
|
30
|
+
/** Generates the app shell and loads framework CSS once, before app styles.
|
|
31
|
+
* The stylesheet participates in Vite CSS HMR and builds; pre-launch paints independently. */
|
|
29
32
|
export function satinApp(app, options = {}) {
|
|
30
33
|
return satinAppFixtures([{ app, query: {} }], options);
|
|
31
34
|
}
|
|
@@ -52,7 +55,6 @@ export function satinAppFixtures(input, options = {}) {
|
|
|
52
55
|
let output = '';
|
|
53
56
|
let publicDirectory = '';
|
|
54
57
|
const covers = new Map();
|
|
55
|
-
const launchIconPreloads = new Set();
|
|
56
58
|
const generatedIcons = new Map();
|
|
57
59
|
const startupIcons = new Map();
|
|
58
60
|
const files = Object.assign({}, ...fixtures.map(({ app, display }) => Object.fromEntries(Object.entries(appManifestFiles(app, appManifestStem(app))).map(([name, source]) => [
|
|
@@ -96,7 +98,7 @@ export function satinAppFixtures(input, options = {}) {
|
|
|
96
98
|
"import { createElement } from 'react'",
|
|
97
99
|
"import { createRoot } from 'react-dom/client'",
|
|
98
100
|
`import Page from ${page}`,
|
|
99
|
-
"createRoot(document.getElementById('satin-install-guide')).render(createElement(Page))",
|
|
101
|
+
"createRoot(document.getElementById('satin-install-guide'), { onUncaughtError: error => window.__SATIN_PRELAUNCH__.error(String(error), 'Install Page Failed') }).render(createElement(Page))",
|
|
100
102
|
].join('\n');
|
|
101
103
|
},
|
|
102
104
|
writeBundle() {
|
|
@@ -198,7 +200,6 @@ export function satinAppFixtures(input, options = {}) {
|
|
|
198
200
|
const inlineIcon = `data:${mime};base64,${iconBytes.toString('base64')}`;
|
|
199
201
|
// Even inline images enter WebKit's image loader asynchronously. Start
|
|
200
202
|
// it in the head so the body cover's first paint already has its icon.
|
|
201
|
-
launchIconPreloads.add(`<link rel="preload" as="image" href="${inlineIcon}">`);
|
|
202
203
|
covers.set(app.id, launchScreenMarkup({
|
|
203
204
|
...app,
|
|
204
205
|
launchScreen: {
|
|
@@ -230,23 +231,7 @@ export function satinAppFixtures(input, options = {}) {
|
|
|
230
231
|
const head = /<head\b[^>]*>(?:\s*<meta\s+charset\s*=\s*[^>]+>)?/i;
|
|
231
232
|
if (!head.test(html))
|
|
232
233
|
throw new Error('Satin app definition requires an index.html head element.');
|
|
233
|
-
|
|
234
|
-
// Single-target apps get real parser markup, including the inline icon.
|
|
235
|
-
// Only the lab's query-selected fixtures need document.write selection.
|
|
236
|
-
const cover = fixtures.length === 1 ? covers.get(fixtures[0].app.id) : '';
|
|
237
|
-
const selectCover = fixtures.length === 1
|
|
238
|
-
? ''
|
|
239
|
-
: `
|
|
240
|
-
const q = new URLSearchParams(location.search);
|
|
241
|
-
const f = ${coverData}.find(f => Object.entries(f.query).every(([k,v]) => q.get(k) === v));
|
|
242
|
-
document.write(f.html);`;
|
|
243
|
-
html = html.replace(/<body\b[^>]*>/i, opening => `${opening}${cover}<script id="satin-launch-cover">(() => {
|
|
244
|
-
if (window.__SATIN_PLATFORM_BLOCKED__ !== false) return;
|
|
245
|
-
${selectCover}
|
|
246
|
-
(${resumeAppliedUpdate.toString()})(window.__SATIN_DEFINITION__.scope,
|
|
247
|
-
window.__SATIN_DEFINITION__.updateProfile === 'release' ? ${JSON.stringify(building ? buildInfo?.appBuildId : undefined)} : undefined);
|
|
248
|
-
})();</script>`);
|
|
249
|
-
return html.replace(head, opening => `${opening}\n${[...launchIconPreloads].join('')}\n${generateAppHead(fixtures, base, production, !building)}`);
|
|
234
|
+
return html.replace(head, opening => `${opening}\n${generateAppHead(fixtures, base, production, !building, covers, building ? buildInfo?.appBuildId : undefined)}\n${frameworkStylesheet(base)}`);
|
|
250
235
|
},
|
|
251
236
|
},
|
|
252
237
|
async closeBundle() {
|
|
@@ -265,7 +250,10 @@ export function satinAppFixtures(input, options = {}) {
|
|
|
265
250
|
await mkdir(resolve(output, installRouteForBase(app, base).slice(base.length)), {
|
|
266
251
|
recursive: true,
|
|
267
252
|
});
|
|
268
|
-
await writeFile(resolve(output, installRouteForBase(app, base).slice(base.length), 'index.html'), installPage(app, 'install', base, {
|
|
253
|
+
await writeFile(resolve(output, installRouteForBase(app, base).slice(base.length), 'index.html'), installPage(app, 'install', base, {
|
|
254
|
+
customPage: customInstallUrl,
|
|
255
|
+
cover: covers.get(app.id),
|
|
256
|
+
}));
|
|
269
257
|
if (app.scope !== base) {
|
|
270
258
|
const html = await readFile(resolve(output, 'index.html'), 'utf8');
|
|
271
259
|
await writeFile(resolve(output, app.scope.slice(base.length), 'index.html'), html);
|
|
@@ -342,7 +330,7 @@ export function satinAppFixtures(input, options = {}) {
|
|
|
342
330
|
const customPage = kind === 'install' && options.installPage
|
|
343
331
|
? base + '@id/__x00__' + CUSTOM_INSTALL_ID
|
|
344
332
|
: undefined;
|
|
345
|
-
let html = installPage(app, kind, base, { customPage });
|
|
333
|
+
let html = installPage(app, kind, base, { customPage, cover: covers.get(app.id) });
|
|
346
334
|
// In dev the custom page is served through Vite (its client and React's refresh).
|
|
347
335
|
if (customPage)
|
|
348
336
|
html = await server.transformIndexHtml(path, html);
|
|
@@ -382,6 +370,7 @@ export function satinAppFixtures(input, options = {}) {
|
|
|
382
370
|
});
|
|
383
371
|
},
|
|
384
372
|
},
|
|
373
|
+
includedStyles(),
|
|
385
374
|
{
|
|
386
375
|
name: 'satin:platform-script-gate',
|
|
387
376
|
transformIndexHtml: {
|
|
@@ -432,6 +421,15 @@ export function satinAppFixtures(input, options = {}) {
|
|
|
432
421
|
for (const { app } of fixtures) {
|
|
433
422
|
await validateBuiltDeployment(output, deploymentExpectation(app, base));
|
|
434
423
|
}
|
|
424
|
+
const policies = {};
|
|
425
|
+
for (const path of [
|
|
426
|
+
fixtures[0].app.startRoute,
|
|
427
|
+
installRouteForBase(fixtures[0].app, base) + '/',
|
|
428
|
+
]) {
|
|
429
|
+
const file = resolve(output, path.slice(base.length), 'index.html');
|
|
430
|
+
policies[path] = inlinePolicySources(await readFile(file, 'utf8'), prelaunchCSS);
|
|
431
|
+
}
|
|
432
|
+
await writeFile(resolve(output, 'satin-csp.json'), JSON.stringify({ version: 1, policies }, null, 2));
|
|
435
433
|
console.info(`Deploy dist/ at ${base}, then run satin check <url>`);
|
|
436
434
|
},
|
|
437
435
|
},
|