shotops-mcp 0.9.7 → 0.9.8
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/README.md +4 -3
- package/dist/local.js +1385 -476
- package/harness-dist/assets/index-D7fH-FSG.js +1 -0
- package/harness-dist/assets/{three-addons-CvX38LO6.js → three-addons-zF8BveTt.js} +1 -1
- package/harness-dist/assets/{three-webgl-DCw804Ic.js → three-webgl-BAyyruEx.js} +2 -2
- package/harness-dist/index.html +3 -3
- package/package.json +1 -1
- package/widget-dist/bundle.js +3 -3
- package/widget-dist/panels.js +2 -2
- package/harness-dist/assets/index--FFK3psO.js +0 -1
package/dist/local.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
await visit('')
|
|
26
26
|
return hash.digest('hex')
|
|
27
|
-
})(sourceDirectory) !== "
|
|
27
|
+
})(sourceDirectory) !== "7345df8a318a3c0989195e1b9c4ce91f2eaf136e27afd8ee123620da60c23410") {
|
|
28
28
|
console.error('shotops-mcp: dist/local.js is stale; run npm run build in shotops-mcp.');
|
|
29
29
|
}
|
|
30
30
|
} catch { /* An unreadable checkout must not break an otherwise working CLI. */ }
|
|
@@ -44,6 +44,27 @@ var __export = (target, all) => {
|
|
|
44
44
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
// ../mockup-engine/appstore/presets.ts
|
|
48
|
+
function presetById(id) {
|
|
49
|
+
return PANEL_PRESETS.find((p) => p.id === id) ?? PANEL_PRESETS[0];
|
|
50
|
+
}
|
|
51
|
+
var PANEL_PRESETS;
|
|
52
|
+
var init_presets = __esm({
|
|
53
|
+
"../mockup-engine/appstore/presets.ts"() {
|
|
54
|
+
"use strict";
|
|
55
|
+
PANEL_PRESETS = [
|
|
56
|
+
{ id: "r69", label: "6.9\u2033 iPhone \xB7 1320\xD72868", width: 1320, height: 2868 },
|
|
57
|
+
{ id: "r65", label: "6.5\u2033 iPhone \xB7 1242\xD72688", width: 1242, height: 2688 },
|
|
58
|
+
{ id: "r55", label: "5.5\u2033 iPhone \xB7 1242\xD72208", width: 1242, height: 2208 },
|
|
59
|
+
// Pixel 10 Pro — a tall marketing canvas matching the 6.9″ iPhone's dimensions so
|
|
60
|
+
// the strip layout + caption scale are identical; only the 3D body differs.
|
|
61
|
+
{ id: "pixel69", label: "Pixel 10 Pro \xB7 1320\xD72868", width: 1320, height: 2868 },
|
|
62
|
+
{ id: "ipad13", label: "13\u2033 iPad \xB7 2064\xD72752", width: 2064, height: 2752 },
|
|
63
|
+
{ id: "ipad129", label: "12.9\u2033 iPad \xB7 2048\xD72732", width: 2048, height: 2732 }
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
47
68
|
// ../mockup-engine/appstore/devices.ts
|
|
48
69
|
function deviceById(id) {
|
|
49
70
|
return DEVICES.find((d) => d.id === id);
|
|
@@ -70,9 +91,23 @@ function deriveOutputs(explicit, panelPresetId2) {
|
|
|
70
91
|
}
|
|
71
92
|
return [DEFAULT_OUTPUT_DEVICE_ID];
|
|
72
93
|
}
|
|
94
|
+
function activeDeviceId(outputs, panelPresetId2) {
|
|
95
|
+
const match = outputs.find((id) => deviceById(id)?.panelPresetId === panelPresetId2);
|
|
96
|
+
return match ?? outputs[0];
|
|
97
|
+
}
|
|
98
|
+
function projectOutputAxis(projectFile) {
|
|
99
|
+
const file = projectFile ?? {};
|
|
100
|
+
const explicit = Array.isArray(file.outputs) ? file.outputs.filter((id) => typeof id === "string") : void 0;
|
|
101
|
+
const outputs = deriveOutputs(explicit, typeof file.panelPresetId === "string" ? file.panelPresetId : void 0);
|
|
102
|
+
const panelPresetId2 = presetById(typeof file.panelPresetId === "string" ? file.panelPresetId : "").id;
|
|
103
|
+
return { outputs, panelPresetId: panelPresetId2, activeDeviceId: activeDeviceId(outputs, panelPresetId2) };
|
|
104
|
+
}
|
|
73
105
|
function screenFamilyForDevice(deviceId) {
|
|
74
106
|
return deviceById(deviceId)?.screenFamily ?? BASE_SCREEN_FAMILY;
|
|
75
107
|
}
|
|
108
|
+
function screenFamilyForPreset(panelPresetId2) {
|
|
109
|
+
return DEVICES.find((d) => d.panelPresetId === panelPresetId2)?.screenFamily ?? BASE_SCREEN_FAMILY;
|
|
110
|
+
}
|
|
76
111
|
function outputScreenFamilies(outputs) {
|
|
77
112
|
const seen = /* @__PURE__ */ new Set();
|
|
78
113
|
const out = [];
|
|
@@ -89,6 +124,7 @@ var IPHONE_MODEL_ID, PIXEL_MODEL_ID, DEVICES, DEFAULT_OUTPUT_DEVICE_ID, BASE_SCR
|
|
|
89
124
|
var init_devices = __esm({
|
|
90
125
|
"../mockup-engine/appstore/devices.ts"() {
|
|
91
126
|
"use strict";
|
|
127
|
+
init_presets();
|
|
92
128
|
IPHONE_MODEL_ID = "iphone-17-pro";
|
|
93
129
|
PIXEL_MODEL_ID = "pixel-10-pro";
|
|
94
130
|
DEVICES = [
|
|
@@ -120,7 +156,10 @@ var init_caption = __esm({
|
|
|
120
156
|
});
|
|
121
157
|
|
|
122
158
|
// ../mockup-engine/appstore/catalog/defaults.ts
|
|
123
|
-
|
|
159
|
+
function deviceSizeFromLegacyPhoneHeight(phoneHeight) {
|
|
160
|
+
return Math.round(phoneHeight / DEVICE_SIZE_BASE_FRACTION * 100) / 100;
|
|
161
|
+
}
|
|
162
|
+
var DEFAULT_LOOK, SHOT_LOOK_DOMAIN, SHOT_LOOK_RANGES, DEVICE_SIZE_BASE_FRACTION, DEFAULT_CAPTION_ANCHOR, DEFAULT_CAPTION_STYLE;
|
|
124
163
|
var init_defaults = __esm({
|
|
125
164
|
"../mockup-engine/appstore/catalog/defaults.ts"() {
|
|
126
165
|
"use strict";
|
|
@@ -138,19 +177,24 @@ var init_defaults = __esm({
|
|
|
138
177
|
// pushPhonesBelowCaptionBands() solved per-render before e0f587c ("Stop captions moving
|
|
139
178
|
// devices") deleted it: captions must never move a device, so the clearance has to be baked
|
|
140
179
|
// into the default instead.
|
|
141
|
-
// deviceTop(v) = (1 -
|
|
180
|
+
// deviceTop(v) = (1 - deviceHeightFraction(deviceSize))/2 + (v/100)/2 [layoutStrip, +y = down]
|
|
142
181
|
// bandBottom(n) = CAPTION_BAND_TOP + n·LINE_HEIGHT·sizePt/PANEL_LOGICAL_HEIGHT_PT
|
|
143
|
-
// At 82% height and a 32pt caption, deviceTop = 18.0% and the device sits FLUSH to the bottom
|
|
182
|
+
// At deviceSize 100 (82% of panel height) and a 32pt caption, deviceTop = 18.0% and the device sits FLUSH to the bottom
|
|
144
183
|
// edge (18.0 + 82 = 100.0). A full 2-line headline's ink ends at 13.6%, so the gap is 4.4%;
|
|
145
184
|
// a 3-line headline still clears by 0.3%. Four lines overlap — that is what Bleed is for, and
|
|
146
185
|
// it buys the room by running the device 10% off the bottom edge. Re-derive via
|
|
147
|
-
// catalog/layouts.ts if sizePt or
|
|
148
|
-
|
|
186
|
+
// catalog/layouts.ts if sizePt or deviceSize changes; its tests are the guard.
|
|
187
|
+
deviceSize: "100",
|
|
149
188
|
hOffset: "0",
|
|
150
189
|
vOffset: "18",
|
|
151
190
|
material: "real",
|
|
152
|
-
|
|
153
|
-
|
|
191
|
+
// Deep Blue on the iPhone, Moonstone on the Pixel (which binds no `blue`, so it falls back to its
|
|
192
|
+
// first finish — deviceFinishes.ts). A product call, not a derived one: the blue body is the one
|
|
193
|
+
// that reads as a device against the widest range of strip backgrounds, where Silver disappeared
|
|
194
|
+
// into a light one. `customColor` starts at the same hex so opening the custom well doesn't jump
|
|
195
|
+
// the phone to a different colour before anyone has picked anything.
|
|
196
|
+
colorway: "blue",
|
|
197
|
+
customColor: "32374A",
|
|
154
198
|
finish: "0",
|
|
155
199
|
clearcoat: "0",
|
|
156
200
|
clayTone: "grey",
|
|
@@ -164,7 +208,7 @@ var init_defaults = __esm({
|
|
|
164
208
|
angle: { valueKind: "angle", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.angle, persistence: "always" },
|
|
165
209
|
cameraPos: { valueKind: "camera-position", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.cameraPos, persistence: "always" },
|
|
166
210
|
roll: { valueKind: "numeric-string", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.roll, persistence: "always" },
|
|
167
|
-
|
|
211
|
+
deviceSize: { valueKind: "numeric-string", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.deviceSize, persistence: "always" },
|
|
168
212
|
hOffset: { valueKind: "numeric-string", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.hOffset, persistence: "always" },
|
|
169
213
|
vOffset: { valueKind: "numeric-string", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.vOffset, persistence: "always" },
|
|
170
214
|
material: { valueKind: "material", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.material, persistence: "always" },
|
|
@@ -176,21 +220,27 @@ var init_defaults = __esm({
|
|
|
176
220
|
clayCustom: { valueKind: "string", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.clayCustom, persistence: "always" },
|
|
177
221
|
flatScreen: { valueKind: "boolean", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.flatScreen, persistence: "always" },
|
|
178
222
|
clipToFrame: { valueKind: "boolean", required: false, defaultSource: "explicit", defaultValue: false, persistence: "omit-default" },
|
|
223
|
+
deviceFrame: { valueKind: "boolean", required: false, defaultSource: "explicit", defaultValue: true, persistence: "omit-default" },
|
|
224
|
+
screenCornerRadius: { valueKind: "numeric-string", required: false, defaultSource: "explicit", defaultValue: "12", persistence: "omit-default" },
|
|
179
225
|
glare: { valueKind: "boolean", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.glare, persistence: "always" },
|
|
180
226
|
lighting: { valueKind: "boolean", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.lighting, persistence: "always" },
|
|
181
227
|
reflections: { valueKind: "boolean", required: true, defaultSource: "default-look", defaultValue: DEFAULT_LOOK.reflections, persistence: "always" }
|
|
182
228
|
};
|
|
183
229
|
SHOT_LOOK_RANGES = {
|
|
184
230
|
roll: { min: -45, max: 45 },
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
// exactly what the Bleed layout template trades headline
|
|
188
|
-
|
|
231
|
+
// Percent of the default device size (see DEVICE_SIZE_BASE_FRACTION): 25–240 is 20–200% of the
|
|
232
|
+
// panel's height. Past ~122 the device is TALLER than the panel and bleeds off an edge — a
|
|
233
|
+
// legitimate, shipped composition, and exactly what the Bleed layout template trades headline
|
|
234
|
+
// room for.
|
|
235
|
+
deviceSize: { min: 25, max: 240 },
|
|
189
236
|
hOffset: { min: -100, max: 100 },
|
|
190
237
|
vOffset: { min: -200, max: 200 },
|
|
191
238
|
finish: { min: 0, max: 1 },
|
|
192
|
-
clearcoat: { min: 0, max: 1 }
|
|
239
|
+
clearcoat: { min: 0, max: 1 },
|
|
240
|
+
// Percent of the flat card's WIDTH (#907); 50 turns the short edges into semicircles.
|
|
241
|
+
screenCornerRadius: { min: 0, max: 50 }
|
|
193
242
|
};
|
|
243
|
+
DEVICE_SIZE_BASE_FRACTION = 0.82;
|
|
194
244
|
DEFAULT_CAPTION_ANCHOR = { x: 0.5, y: 0.06 };
|
|
195
245
|
DEFAULT_CAPTION_STYLE = {
|
|
196
246
|
fontId: "inter",
|
|
@@ -362,6 +412,9 @@ function variantKey(variant) {
|
|
|
362
412
|
if (variant.locale !== void 0) parts.push(`locale=${variant.locale}`);
|
|
363
413
|
return parts.join("&");
|
|
364
414
|
}
|
|
415
|
+
function isBaseVariant(variant) {
|
|
416
|
+
return variantKey(variant) === "";
|
|
417
|
+
}
|
|
365
418
|
function variantAxes(variant) {
|
|
366
419
|
const out = [];
|
|
367
420
|
if (variant.family !== void 0) out.push("family");
|
|
@@ -540,9 +593,9 @@ function sha256Hex(text3) {
|
|
|
540
593
|
const words = [h0, h1, h2, h3, h4, h5, h6, h7];
|
|
541
594
|
let out = "";
|
|
542
595
|
for (let i = 0; i < words.length; i += 1) {
|
|
543
|
-
let
|
|
544
|
-
while (
|
|
545
|
-
out +=
|
|
596
|
+
let hex2 = (words[i] >>> 0).toString(16);
|
|
597
|
+
while (hex2.length < 8) hex2 = `0${hex2}`;
|
|
598
|
+
out += hex2;
|
|
546
599
|
}
|
|
547
600
|
return out;
|
|
548
601
|
}
|
|
@@ -1945,7 +1998,7 @@ var init_refineIntent = __esm({
|
|
|
1945
1998
|
init_defaults();
|
|
1946
1999
|
init_fonts();
|
|
1947
2000
|
MAX_REFINE_PROMPT_LENGTH = 1200;
|
|
1948
|
-
AI_COLORWAY_IDS = ["orange", "blue", "silver", "green"];
|
|
2001
|
+
AI_COLORWAY_IDS = ["orange", "blue", "ice", "silver", "red", "black", "green"];
|
|
1949
2002
|
AI_CLAY_TONES = ["grey", "white", "charcoal"];
|
|
1950
2003
|
AI_SHOT_RANGES = {
|
|
1951
2004
|
orbit: { min: -180, max: 180 },
|
|
@@ -1982,7 +2035,7 @@ var init_shotLookProjection = __esm({
|
|
|
1982
2035
|
valueKind: "camera-position",
|
|
1983
2036
|
validation: { wireKind: "nullable-camera-position" },
|
|
1984
2037
|
schemaDescription: "Manual camera position override; null/omit = use the angle preset.",
|
|
1985
|
-
catalogDescription: "{ x, y, z } rotates the camera around the device to refine the angle beyond the presets, or null to use the preset. It does NOT zoom or resize \u2014 device SIZE is set ONLY by
|
|
2038
|
+
catalogDescription: "{ x, y, z } rotates the camera around the device to refine the angle beyond the presets, or null to use the preset. It does NOT zoom or resize \u2014 device SIZE is set ONLY by deviceSize (the export alpha-crops the device tight, so camera distance is discarded)."
|
|
1986
2039
|
},
|
|
1987
2040
|
roll: {
|
|
1988
2041
|
exposure: "public",
|
|
@@ -1991,12 +2044,12 @@ var init_shotLookProjection = __esm({
|
|
|
1991
2044
|
schemaDescription: `${rangeText("roll")}\xB0 clock-hand tilt. Default ${defaultValue("roll")}.`,
|
|
1992
2045
|
catalogDescription: `${catalogRange("roll")} (degrees, clock-hand tilt; continuous, unlike angle)`
|
|
1993
2046
|
},
|
|
1994
|
-
|
|
2047
|
+
deviceSize: {
|
|
1995
2048
|
exposure: "public",
|
|
1996
2049
|
valueKind: "numeric-string",
|
|
1997
2050
|
validation: { wireKind: "bounded-numeric" },
|
|
1998
|
-
schemaDescription: `
|
|
1999
|
-
catalogDescription: `${catalogRange("
|
|
2051
|
+
schemaDescription: `Device size, ${rangeText("deviceSize")} (percent of the default device size; about 122 fills the panel height and more bleeds off an edge). Default ${defaultValue("deviceSize")} (the Standard layout template; prefer \`style.layout\` over setting this by hand).`,
|
|
2052
|
+
catalogDescription: `${catalogRange("deviceSize")} (percent of the default device size, which is 82% of the panel height) \u2014 the ONLY device-size control. Past about 122 the device is taller than the panel and bleeds off an edge. Default ${defaultValue("deviceSize")} (the Standard layout template; prefer style.layout over setting this by hand).`
|
|
2000
2053
|
},
|
|
2001
2054
|
hOffset: {
|
|
2002
2055
|
exposure: "public",
|
|
@@ -2022,9 +2075,9 @@ var init_shotLookProjection = __esm({
|
|
|
2022
2075
|
colorway: {
|
|
2023
2076
|
exposure: "public",
|
|
2024
2077
|
valueKind: "colorway",
|
|
2025
|
-
validation: { wireKind: "enum", values: ["orange", "blue", "silver", "green", "custom"] },
|
|
2026
|
-
schemaDescription: `Body colour (material "real"). Default ${defaultValue("colorway")}. These are finish SLOTS \u2014 each device binds its own real finish
|
|
2027
|
-
catalogDescription: "orange | blue | silver | green | custom (with customColor hex) \u2014 finish SLOTS, each device binds its own real finish
|
|
2078
|
+
validation: { wireKind: "enum", values: ["orange", "blue", "ice", "silver", "red", "black", "green", "custom"] },
|
|
2079
|
+
schemaDescription: `Body colour (material "real"). Default ${defaultValue("colorway")}. These are finish SLOTS \u2014 each device binds its own real finish. On iPhone: blue = Deep Blue, silver = Silver, orange = Cosmic Orange, ice = Glacier, red = Burgundy, black = Black. A slot a device does not bind falls back to its first finish \u2014 "green" is Pixel-only (Jade).`,
|
|
2080
|
+
catalogDescription: "orange | blue | ice | silver | red | black | green | custom (with customColor hex) \u2014 finish SLOTS, each device binds its own real finish. On iPhone: blue = Deep Blue, silver = Silver, orange = Cosmic Orange, ice = Glacier, red = Burgundy, black = Black. A slot a device does not bind falls back to its first finish; green is Pixel-only (Jade)."
|
|
2028
2081
|
},
|
|
2029
2082
|
customColor: {
|
|
2030
2083
|
exposure: "public",
|
|
@@ -2095,6 +2148,20 @@ var init_shotLookProjection = __esm({
|
|
|
2095
2148
|
validation: { wireKind: "boolean" },
|
|
2096
2149
|
schemaDescription: `Clip this complete device composite to its owning frame. Default ${defaultValue("clipToFrame")} (overflow allowed).`,
|
|
2097
2150
|
catalogDescription: "boolean \u2014 clip this device, its screen, shadow and reflection to its owning frame; default false"
|
|
2151
|
+
},
|
|
2152
|
+
deviceFrame: {
|
|
2153
|
+
exposure: "public",
|
|
2154
|
+
valueKind: "boolean",
|
|
2155
|
+
validation: { wireKind: "boolean" },
|
|
2156
|
+
schemaDescription: `Render the 3D device frame around the screen. Default ${defaultValue("deviceFrame")}. Set false for a flat rounded screenshot card with no device chrome \u2014 screenCornerRadius then sets its corner rounding, and roll still rotates the flat card exactly as it tilts a framed device.`,
|
|
2157
|
+
catalogDescription: `boolean \u2014 false renders a flat rounded screenshot card instead of the 3D device; screenCornerRadius then sets its corner rounding, and roll still rotates the flat card. Default true.`
|
|
2158
|
+
},
|
|
2159
|
+
screenCornerRadius: {
|
|
2160
|
+
exposure: "public",
|
|
2161
|
+
valueKind: "numeric-string",
|
|
2162
|
+
validation: { wireKind: "bounded-numeric" },
|
|
2163
|
+
schemaDescription: `${rangeText("screenCornerRadius")} (percent of the flat card's width). Default ${defaultValue("screenCornerRadius")}. Applies only when deviceFrame is false \u2014 ignored on a framed device, whose corners come from the device model.`,
|
|
2164
|
+
catalogDescription: `${catalogRange("screenCornerRadius")} (percent of the flat card's width) \u2014 applies only when deviceFrame is false; ignored on a framed device.`
|
|
2098
2165
|
}
|
|
2099
2166
|
};
|
|
2100
2167
|
MCP_SHOT_LOOK_PUBLIC_ENTRIES = Object.entries(MCP_SHOT_LOOK_PROJECTION).filter((entry) => entry[1].exposure === "public");
|
|
@@ -2104,24 +2171,12 @@ var init_shotLookProjection = __esm({
|
|
|
2104
2171
|
}
|
|
2105
2172
|
});
|
|
2106
2173
|
|
|
2107
|
-
// ../mockup-engine/
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
var PANEL_PRESETS;
|
|
2112
|
-
var init_presets = __esm({
|
|
2113
|
-
"../mockup-engine/appstore/presets.ts"() {
|
|
2174
|
+
// ../mockup-engine/outputDeviceIds.ts
|
|
2175
|
+
var OUTPUT_DEVICE_IDS;
|
|
2176
|
+
var init_outputDeviceIds = __esm({
|
|
2177
|
+
"../mockup-engine/outputDeviceIds.ts"() {
|
|
2114
2178
|
"use strict";
|
|
2115
|
-
|
|
2116
|
-
{ id: "r69", label: "6.9\u2033 iPhone \xB7 1290\xD72796", width: 1290, height: 2796 },
|
|
2117
|
-
{ id: "r65", label: "6.5\u2033 iPhone \xB7 1242\xD72688", width: 1242, height: 2688 },
|
|
2118
|
-
{ id: "r55", label: "5.5\u2033 iPhone \xB7 1242\xD72208", width: 1242, height: 2208 },
|
|
2119
|
-
// Pixel 10 Pro — a tall marketing canvas matching the 6.9″ iPhone's dimensions so
|
|
2120
|
-
// the strip layout + caption scale are identical; only the 3D body differs.
|
|
2121
|
-
{ id: "pixel69", label: "Pixel 10 Pro \xB7 1290\xD72796", width: 1290, height: 2796 },
|
|
2122
|
-
{ id: "ipad13", label: "13\u2033 iPad \xB7 2064\xD72752", width: 2064, height: 2752 },
|
|
2123
|
-
{ id: "ipad129", label: "12.9\u2033 iPad \xB7 2048\xD72732", width: 2048, height: 2732 }
|
|
2124
|
-
];
|
|
2179
|
+
OUTPUT_DEVICE_IDS = ["iphone-6-9", "iphone-6-5"];
|
|
2125
2180
|
}
|
|
2126
2181
|
});
|
|
2127
2182
|
|
|
@@ -2157,20 +2212,33 @@ function panelLabelFor(presetId, width, height) {
|
|
|
2157
2212
|
function panelDimensionsFor(presetId) {
|
|
2158
2213
|
return PANEL_DIMENSIONS[presetId] ?? PANEL_DIMENSIONS.r69;
|
|
2159
2214
|
}
|
|
2160
|
-
var MCP_UNKNOWN_PANEL_PRESET_IDS, MCP_PANEL_PRESETS, PANEL_PRESET_IDS, RENDERABLE_PANEL_PRESET_IDS,
|
|
2215
|
+
var MCP_UNRENDERED_DEVICE_IDS, MCP_UNKNOWN_PANEL_PRESET_IDS, MCP_PANEL_PRESETS, PANEL_PRESET_IDS, RENDERABLE_PANEL_PRESET_IDS, OUTPUT_DEVICES, NOT_COVERED_BY_THIS_CALL, SIZE_SUFFIX, PANEL_LABELS, PANEL_DIMENSIONS;
|
|
2161
2216
|
var init_panelCatalog = __esm({
|
|
2162
2217
|
"src/render/panelCatalog.ts"() {
|
|
2163
2218
|
"use strict";
|
|
2164
2219
|
init_presets();
|
|
2165
|
-
|
|
2220
|
+
init_devices();
|
|
2221
|
+
init_outputDeviceIds();
|
|
2222
|
+
init_outputDeviceIds();
|
|
2223
|
+
MCP_UNRENDERED_DEVICE_IDS = ["pixel-10-pro"];
|
|
2224
|
+
MCP_UNKNOWN_PANEL_PRESET_IDS = MCP_UNRENDERED_DEVICE_IDS.map(
|
|
2225
|
+
(id) => deviceById(id)?.panelPresetId ?? id
|
|
2226
|
+
);
|
|
2166
2227
|
MCP_PANEL_PRESETS = PANEL_PRESETS.filter((preset) => !MCP_UNKNOWN_PANEL_PRESET_IDS.includes(preset.id));
|
|
2167
2228
|
PANEL_PRESET_IDS = MCP_PANEL_PRESETS.map((preset) => preset.id);
|
|
2168
2229
|
RENDERABLE_PANEL_PRESET_IDS = ["r69", "r65", "r55"];
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2230
|
+
OUTPUT_DEVICES = OUTPUT_DEVICE_IDS.map((id) => {
|
|
2231
|
+
const device = deviceById(id);
|
|
2232
|
+
if (!device || device.comingSoon) {
|
|
2233
|
+
throw new Error(`output device "${id}" is not a selectable device in the engine catalog`);
|
|
2234
|
+
}
|
|
2235
|
+
return {
|
|
2236
|
+
id,
|
|
2237
|
+
label: device.label,
|
|
2238
|
+
panelPresetId: device.panelPresetId,
|
|
2239
|
+
ascDeviceSet: device.ascDeviceSet
|
|
2240
|
+
};
|
|
2241
|
+
});
|
|
2174
2242
|
NOT_COVERED_BY_THIS_CALL = "not covered by this bundle";
|
|
2175
2243
|
SIZE_SUFFIX = / · \d+×\d+$/;
|
|
2176
2244
|
PANEL_LABELS = Object.fromEntries(
|
|
@@ -2219,7 +2287,7 @@ var init_layouts = __esm({
|
|
|
2219
2287
|
blurb: "Headline on top, whole device below it, nothing cropped. The safe default.",
|
|
2220
2288
|
character: "safe",
|
|
2221
2289
|
bleedEdge: "none",
|
|
2222
|
-
shotLook: {
|
|
2290
|
+
shotLook: { deviceSize: "100", hOffset: "0", vOffset: "18", angle: "front", cameraPos: null, roll: "0" },
|
|
2223
2291
|
caption: { sizePt: 32, maxWidth: 0.86, reserveLines: 2, align: "center", bandCenter: 0.5 }
|
|
2224
2292
|
},
|
|
2225
2293
|
{
|
|
@@ -2228,7 +2296,7 @@ var init_layouts = __esm({
|
|
|
2228
2296
|
blurb: "Room for a longer headline, device running off the bottom edge. What the market looks like.",
|
|
2229
2297
|
character: "conventional",
|
|
2230
2298
|
bleedEdge: "bottom",
|
|
2231
|
-
shotLook: {
|
|
2299
|
+
shotLook: { deviceSize: "100", hOffset: "0", vOffset: "38", angle: "front", cameraPos: null, roll: "0" },
|
|
2232
2300
|
caption: { sizePt: 32, maxWidth: 0.86, reserveLines: 4, align: "center", bandCenter: 0.5 }
|
|
2233
2301
|
},
|
|
2234
2302
|
{
|
|
@@ -2237,7 +2305,7 @@ var init_layouts = __esm({
|
|
|
2237
2305
|
blurb: "Device running off the TOP edge, headline underneath it. Inverts the usual reading order.",
|
|
2238
2306
|
character: "expressive",
|
|
2239
2307
|
bleedEdge: "top",
|
|
2240
|
-
shotLook: {
|
|
2308
|
+
shotLook: { deviceSize: "100", hOffset: "0", vOffset: "-38", angle: "front", cameraPos: null, roll: "0" },
|
|
2241
2309
|
// The only entry that moves the band. Its caption sits BELOW the device, which the engine
|
|
2242
2310
|
// could not express at all until #218 gave placeCaptions a bottom-anchored band — before that
|
|
2243
2311
|
// the only way down was pinning an anchor, which silently costs reflow, stacking and the
|
|
@@ -2256,7 +2324,7 @@ var init_layouts = __esm({
|
|
|
2256
2324
|
blurb: "Device pushed off the RIGHT edge, headline in a column down the left. Reads as one continuous swipe.",
|
|
2257
2325
|
character: "expressive",
|
|
2258
2326
|
bleedEdge: "right",
|
|
2259
|
-
shotLook: {
|
|
2327
|
+
shotLook: { deviceSize: "100", hOffset: "46", vOffset: "18", angle: "front", cameraPos: null, roll: "0" },
|
|
2260
2328
|
caption: { sizePt: 32, maxWidth: 0.61, reserveLines: 2, align: "left", bandCenter: 0.375 }
|
|
2261
2329
|
},
|
|
2262
2330
|
{
|
|
@@ -2265,7 +2333,7 @@ var init_layouts = __esm({
|
|
|
2265
2333
|
blurb: "Device pushed off the LEFT edge, headline in a column down the right. The mirror of Lean right.",
|
|
2266
2334
|
character: "expressive",
|
|
2267
2335
|
bleedEdge: "left",
|
|
2268
|
-
shotLook: {
|
|
2336
|
+
shotLook: { deviceSize: "100", hOffset: "-46", vOffset: "18", angle: "front", cameraPos: null, roll: "0" },
|
|
2269
2337
|
caption: { sizePt: 32, maxWidth: 0.61, reserveLines: 2, align: "right", bandCenter: 0.625 }
|
|
2270
2338
|
},
|
|
2271
2339
|
{
|
|
@@ -2279,7 +2347,7 @@ var init_layouts = __esm({
|
|
|
2279
2347
|
// too — which makes this template claim `bottom-right` and collide with Corner bleed, the only
|
|
2280
2348
|
// other entry on that edge. The horizontal push is the whole idea here; the vertical stays out
|
|
2281
2349
|
// of its way. Both halves are guarded (clearance, and the declared-edge check).
|
|
2282
|
-
shotLook: {
|
|
2350
|
+
shotLook: { deviceSize: "100", hOffset: "78", vOffset: "18", angle: "front", cameraPos: null, roll: "0" },
|
|
2283
2351
|
caption: { sizePt: 32, maxWidth: 0.86, reserveLines: 2, align: "center", bandCenter: 0.5 }
|
|
2284
2352
|
},
|
|
2285
2353
|
{
|
|
@@ -2288,7 +2356,7 @@ var init_layouts = __esm({
|
|
|
2288
2356
|
blurb: "Off the bottom AND the right at once, headline in a column down the left. The most opinionated panel here.",
|
|
2289
2357
|
character: "expressive",
|
|
2290
2358
|
bleedEdge: "bottom-right",
|
|
2291
|
-
shotLook: {
|
|
2359
|
+
shotLook: { deviceSize: "100", hOffset: "46", vOffset: "38", angle: "front", cameraPos: null, roll: "0" },
|
|
2292
2360
|
caption: { sizePt: 32, maxWidth: 0.61, reserveLines: 4, align: "left", bandCenter: 0.375 }
|
|
2293
2361
|
}
|
|
2294
2362
|
];
|
|
@@ -2302,7 +2370,7 @@ var init_layouts = __esm({
|
|
|
2302
2370
|
align: "center",
|
|
2303
2371
|
bandCenter: 0.5
|
|
2304
2372
|
};
|
|
2305
|
-
LAYOUT_POSE_FIELDS = ["
|
|
2373
|
+
LAYOUT_POSE_FIELDS = ["deviceSize", "hOffset", "vOffset"];
|
|
2306
2374
|
LAYOUT_CAMERA_FIELDS = ["angle", "cameraPos", "roll"];
|
|
2307
2375
|
LAYOUT_CAPTION_FIELDS = Array.from(
|
|
2308
2376
|
new Set(LAYOUT_TEMPLATE_CATALOG.flatMap((t) => Object.keys(t.caption)))
|
|
@@ -2314,7 +2382,8 @@ var init_layouts = __esm({
|
|
|
2314
2382
|
function layoutTemplateAgentSummary(template) {
|
|
2315
2383
|
const reserveLines = template.caption.reserveLines;
|
|
2316
2384
|
const capacity = typeof reserveLines === "number" ? ` Reserves ${reserveLines} headline lines (~${Math.round(reserveLines * 18 * (template.caption.maxWidth ?? 0.86) / 0.86)} characters).` : "";
|
|
2317
|
-
|
|
2385
|
+
const headline = (template.caption.maxWidth ?? 0.86) < 0.86 || (template.caption.align ?? "center") !== "center" ? " Restyles the headline into a narrower column." : " Leaves the headline full-width and centred.";
|
|
2386
|
+
return `${template.blurb}${capacity}${headline} Character: [${template.character}]. Bleeds: [${template.bleedEdge}].`;
|
|
2318
2387
|
}
|
|
2319
2388
|
function layoutTemplateById(id) {
|
|
2320
2389
|
return LAYOUT_TEMPLATES.find((t) => t.id === id);
|
|
@@ -2406,12 +2475,20 @@ var init_layoutTemplates = __esm({
|
|
|
2406
2475
|
'A request that names a composition outright ("device running off the bottom edge", "headline under the phone") beats the character.',
|
|
2407
2476
|
"The EDGE beats the character too, and is checked first: a request that says which way the phone should go has already chosen, whatever quality words sit around it.",
|
|
2408
2477
|
"When several templates share the right character AND the right edge, take the one whose reserved headline lines fit the LONGEST caption in the set. Under-reserving truncates; over-reserving leaves a visible empty band.",
|
|
2409
|
-
"A side bleed
|
|
2478
|
+
"A side bleed may be given to the whole set or to named frames, but never to a frame whose NEIGHBOUR leans the other way. Every panel leaning the same way reads as one swipe; mirrored neighbours (lean-right beside lean-left) overlap by most of a panel width and read as a mistake. Alternate a lean with `standard`, never with its mirror.",
|
|
2479
|
+
"A template sets the HEADLINE as well as the phone, so choosing one restyles the captions on every frame it reaches. When the user asked for a composition and asked NOT to touch the captions, take the template on the right edge whose headline treatment already matches theirs, and say in the interpretation what it does to the headline."
|
|
2410
2480
|
];
|
|
2411
2481
|
valuesEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
2412
2482
|
}
|
|
2413
2483
|
});
|
|
2414
2484
|
|
|
2485
|
+
// ../mockup-engine/hexColor.ts
|
|
2486
|
+
var init_hexColor = __esm({
|
|
2487
|
+
"../mockup-engine/hexColor.ts"() {
|
|
2488
|
+
"use strict";
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2491
|
+
|
|
2415
2492
|
// ../mockup-engine/gradient.ts
|
|
2416
2493
|
function angleFromDirection(dir) {
|
|
2417
2494
|
return dir === "horizontal" ? HORIZONTAL_ANGLE : VERTICAL_ANGLE;
|
|
@@ -2470,6 +2547,7 @@ var HORIZONTAL_ANGLE, VERTICAL_ANGLE, FALLBACK_GRADIENT_FROM, FALLBACK_GRADIENT_
|
|
|
2470
2547
|
var init_gradient = __esm({
|
|
2471
2548
|
"../mockup-engine/gradient.ts"() {
|
|
2472
2549
|
"use strict";
|
|
2550
|
+
init_hexColor();
|
|
2473
2551
|
HORIZONTAL_ANGLE = 90;
|
|
2474
2552
|
VERTICAL_ANGLE = 180;
|
|
2475
2553
|
FALLBACK_GRADIENT_FROM = "#1b1b2e";
|
|
@@ -2766,7 +2844,7 @@ var init_toolContract = __esm({
|
|
|
2766
2844
|
"device, if the project targets several), so the Big-jobs timing warning applies unchanged.",
|
|
2767
2845
|
"",
|
|
2768
2846
|
"## Outputs \u2014 one project, several device SIZES",
|
|
2769
|
-
"A project targets one or more DEVICES: iphone-6-9 (
|
|
2847
|
+
"A project targets one or more DEVICES: iphone-6-9 (1320\xD72868) and/or iphone-6-5 (1242\xD72688).",
|
|
2770
2848
|
"You almost never need to name them. render_project renders EVERY device the project targets \u2014",
|
|
2771
2849
|
"the top-level panels are the previewed device, and an `outputs` array carries each device's own",
|
|
2772
2850
|
"panels when there is more than one. emit_bundle does the same for the zip: with a `project` and",
|
|
@@ -2815,7 +2893,7 @@ var init_toolContract = __esm({
|
|
|
2815
2893
|
"COMPOSE \u2014 every real App Store strip needs BOTH varied devices AND headlines, and that is ONE",
|
|
2816
2894
|
"call: pass `look` (devices + background) and `style` (style.captions gives the words) together.",
|
|
2817
2895
|
"A style.shotLook/style.background alongside a look is redundant \u2014 the look wins, with a note.",
|
|
2818
|
-
"panelPresetId picks the App Store size: r69 (6.9\u2033, default,
|
|
2896
|
+
"panelPresetId picks the App Store size: r69 (6.9\u2033, default, 1320\xD72868) | r65 | r55 \u2014 iPhone only.",
|
|
2819
2897
|
"Match it to your source screenshots (1320\xD72868 or 1290\xD72796 \u2192 r69).",
|
|
2820
2898
|
"Continuous scene: panels have no hard divider, so a phone pushed past its edge with a large",
|
|
2821
2899
|
"hOffset overflows into the NEIGHBOURING panel and the strip reads as one swipe-through image.",
|
|
@@ -2901,6 +2979,21 @@ var init_toolContract = __esm({
|
|
|
2901
2979
|
"Skip the names and the record falls back to positional filenames (screen-1.png, screen-2.png\u2026)",
|
|
2902
2980
|
"the user's folder can't match \u2014 the single worst create-project failure mode. Pass the real filenames.",
|
|
2903
2981
|
"",
|
|
2982
|
+
"## Link the project to the real app (search_app_listing)",
|
|
2983
|
+
"When the user says which app these screenshots are for, find it instead of typing its name:",
|
|
2984
|
+
'search_app_listing({ term: "Duolingo" }) returns matching App Store apps with their exact name,',
|
|
2985
|
+
"seller, icon, bundle ID and numeric app ID, plus the App Store Connect languages and iPhone",
|
|
2986
|
+
"devices ShotOps would apply. Show the candidates and let the USER say which one is theirs \u2014 the",
|
|
2987
|
+
"icon and the seller are what prove it \u2014 then pass that result's `appId` (or its `storeUrl`, which",
|
|
2988
|
+
"also carries the storefront) as save_project({ appListing }). The created project then takes the",
|
|
2989
|
+
"app's name, bundle ID, language list and device set; anything you state yourself \u2014 `projectName`,",
|
|
2990
|
+
"`outputs`, `locale` \u2014 wins, and the app is still linked. Passing `appListing` with an existing",
|
|
2991
|
+
"`project` re-links that project and rewrites nothing else. read_project reports the linked app as",
|
|
2992
|
+
"`linkedApp`. Both are free reads over Apple's public listing data: no Apple or store credential is",
|
|
2993
|
+
"involved, and a public listing proves nothing about who owns the app \u2014 the bundle ID stays",
|
|
2994
|
+
"editable. Both need an account; an unsigned local server says so and the save still works without",
|
|
2995
|
+
"the reference.",
|
|
2996
|
+
"",
|
|
2904
2997
|
'## "Any updates?" \u2014 read back what a designer changed (read_project)',
|
|
2905
2998
|
"read_look returns only STYLING. To see what a designer edited in the web app \u2014 frame ORDER, the",
|
|
2906
2999
|
"per-locale caption WORDS, the locale list, structural changes \u2014 call read_project. It returns the",
|
|
@@ -3027,7 +3120,7 @@ var init_designIntake = __esm({
|
|
|
3027
3120
|
{ id: "continuous", label: "One continuous scene across panels", blurb: "Large hOffset values let a phone straddle the seam so the strip reads as one swipe-through image. Preview it \u2014 placement is fiddly." },
|
|
3028
3121
|
// #223 — the bound is read from the engine's range table, not re-typed: this blurb said
|
|
3029
3122
|
// 20–100 while the render clamped to 200 and both sliders offered it.
|
|
3030
|
-
{ id: "bigger", label: "Make the phones larger or smaller", blurb: `
|
|
3123
|
+
{ id: "bigger", label: "Make the phones larger or smaller", blurb: `deviceSize, ${SHOT_LOOK_RANGES.deviceSize.min}\u2013${SHOT_LOOK_RANGES.deviceSize.max} (percent of the default device size; about 122 fills the panel height and more bleeds off an edge), default ${DEFAULT_LOOK.deviceSize}. It is the ONLY device-size control \u2014 cameraPos does not resize. Prefer style.layout over hand-setting it.` }
|
|
3031
3124
|
],
|
|
3032
3125
|
apply: `The layout answer is one field: style.layout: ${LAYOUT_TEMPLATE_IDS.map((id) => `"${id}"`).join(" | ")} (see describe_look \`layouts\`). It expands FIRST into ` + LAYOUT_DEVICE_PATHS + " + " + LAYOUT_CAPTION_PATHS + ', and any of those fields you pass explicitly still overrides it \u2014 so "bleed, but a bit lower" is { layout: "bleed", shotLook: { vOffset: "45" } }. Nothing stores the template id. Anything past "uniform" on the device half means passing a `look` alongside `style`. They compose in one render_strip call: the look supplies per-device styling + background, style.captions the words.'
|
|
3033
3126
|
}
|
|
@@ -3137,11 +3230,12 @@ var init_lookSchemas = __esm({
|
|
|
3137
3230
|
rangeText2 = (field) => `${SHOT_LOOK_RANGES[field].min}\u2013${SHOT_LOOK_RANGES[field].max}`;
|
|
3138
3231
|
RANGE_UNITS = {
|
|
3139
3232
|
roll: "degrees of clock-hand tilt",
|
|
3140
|
-
|
|
3233
|
+
deviceSize: "percent of the default device size, 100 = default",
|
|
3141
3234
|
hOffset: "horizontal position, 0 = centre",
|
|
3142
3235
|
vOffset: "vertical position, 0 = centre, positive = lower",
|
|
3143
3236
|
finish: "0 = matte \u2026 1 = glossy",
|
|
3144
|
-
clearcoat: "0 = none \u2026 1 = full clear-coat gloss"
|
|
3237
|
+
clearcoat: "0 = none \u2026 1 = full clear-coat gloss",
|
|
3238
|
+
screenCornerRadius: "percent of the flat card width"
|
|
3145
3239
|
};
|
|
3146
3240
|
PLACEMENT_FIELDS = LAYOUT_POSE_FIELDS;
|
|
3147
3241
|
layoutAlternative = () => ` For a ready-made composition pass \`style.layout\`: ${LAYOUT_TEMPLATE_IDS.map((id) => `"${id}"`).join(" | ")} \u2014 it sets ${LAYOUT_POSE_FIELDS.join("/")} together, resets camera pose + roll, and sets the headline region they were solved against), and any field you also pass explicitly still wins over it.`;
|
|
@@ -3264,7 +3358,7 @@ var init_lookSchemas = __esm({
|
|
|
3264
3358
|
// write door — saveLookForProject in api/_lib/projectLookStore.ts — so this field is purely
|
|
3265
3359
|
// informational on the wire.
|
|
3266
3360
|
schema: z3.number().optional().describe("Look schema version. The server stamps it on every save (save_look); read_look returns it. Any input value is ignored \u2014 never set it yourself."),
|
|
3267
|
-
panelPresetId: z3.string().optional().describe("Canvas size preset, e.g. r69 =
|
|
3361
|
+
panelPresetId: z3.string().optional().describe("Canvas size preset, e.g. r69 = 1320\xD72868. Default r69."),
|
|
3268
3362
|
bgMode: z3.enum(["gradient", "perPanel"]).optional().describe("One gradient across the strip, or a flat colour per panel. Default gradient."),
|
|
3269
3363
|
// #460 decision 6 — UNLIKE render_strip's `style.background` above, the PERSISTED look keeps
|
|
3270
3364
|
// both forms: `gradientStops`/`gradientAngle` are canonical (win when present), and
|
|
@@ -3543,8 +3637,8 @@ var init_describeLookCatalog = __esm({
|
|
|
3543
3637
|
gradientFrom: "#3E3D42",
|
|
3544
3638
|
gradientTo: "#1E1D22",
|
|
3545
3639
|
shots: [
|
|
3546
|
-
{ panelId: "panel-1", look: { material: "real", colorway: "blue", angle: "left", roll: "-19",
|
|
3547
|
-
{ panelId: "panel-1", look: { material: "real", colorway: "orange", angle: "right", roll: "8",
|
|
3640
|
+
{ panelId: "panel-1", look: { material: "real", colorway: "blue", angle: "left", roll: "-19", deviceSize: "90" } },
|
|
3641
|
+
{ panelId: "panel-1", look: { material: "real", colorway: "orange", angle: "right", roll: "8", deviceSize: "105" } }
|
|
3548
3642
|
]
|
|
3549
3643
|
}
|
|
3550
3644
|
},
|
|
@@ -3570,7 +3664,7 @@ var init_describeLookCatalog = __esm({
|
|
|
3570
3664
|
"style.layout PRECEDENCE: the template expands first, then any explicit shotLook composition field or caption layout field you also pass overrides it. Nothing stores the template id \u2014 save_project persists the expanded values and read_look returns them unchanged, so a layout is a starting point exactly as a preset is.",
|
|
3571
3665
|
"style.shotLook is ONE shared style for every phone. To vary ANY devices \u2014 including two phones inside the SAME panel \u2014 pass a `look` instead. `look.shots[]` has one entry per screenshot/device in flattened panel order; repeat panelId for devices that share a panel. See the `look` example.",
|
|
3572
3666
|
"COMPOSE (every real App Store strip): to style devices independently AND caption their panels, pass BOTH `look` (per-device styling via shots[] + background) and `style` (captions via style.captions) in ONE render_strip/emit_bundle call \u2014 they combine. The look supplies the styling; if you also pass style.shotLook/style.background alongside a look they are ignored (the look wins) and a note tells you.",
|
|
3573
|
-
"Fidelity ceilings: `angle` is preset-only (front|left|right) and `roll` is continuous (\xB145\xB0), but there is NO free yaw \u2014 an arbitrary 3-axis source rotation can only be approximated. Device SIZE is set only by `
|
|
3667
|
+
"Fidelity ceilings: `angle` is preset-only (front|left|right) and `roll` is continuous (\xB145\xB0), but there is NO free yaw \u2014 an arbitrary 3-axis source rotation can only be approximated. Device SIZE is set only by `deviceSize` (there is no separate scale, and cameraPos does not resize). Fonts are the 5 in `style.captions.fonts` \u2014 pick the closest; an exact non-bundled typeface match is not possible.",
|
|
3574
3668
|
"save_look appends a look version; hold_look sets which saved version agents render by default (or Hold one in ShotOps\u2019s Version history), and render_strip `version` renders a specific one."
|
|
3575
3669
|
]
|
|
3576
3670
|
};
|
|
@@ -3579,7 +3673,7 @@ var init_describeLookCatalog = __esm({
|
|
|
3579
3673
|
|
|
3580
3674
|
// src/contracts/outputSchemas.ts
|
|
3581
3675
|
import { z as z4 } from "zod";
|
|
3582
|
-
var projectSummary, storedAssetOutput, panelDeliveryOutput, panelOutput, lookHistoryAccess, noteField, creditsField, layoutFindingOutput, layoutAdvisoryOutput, contractField, disclosureField, operationStateOutputSchema, durableRenderFinalOutputSchema, durableBundleFinalOutputSchema, renderStripOutputSchema, optionalShape, operationStateShape, renderStripToolOutputSchema, emitBundleOutputSchema, emitBundleToolOutputSchema, saveProjectOutputSchema, layoutFieldDiff, layoutReportShape, layoutReportOutput, readLookOutputSchema, storedScreenshots, readProjectOutputSchema, agentOutcomeOutput, refineProjectOutputSchema, renderProjectOutputSchema, renderProjectToolOutputSchema, productionOperationOutputSchema, saveLookOutputSchema, holdLookOutputSchema, releaseLookOutputSchema, describeLookOutputSchema, accountCreditBalanceSchema, accountStatusOutputSchema, requestScreenshotUploadOutputSchema, importedScreenshotOutput, importScreenshotOutputSchema, deleteAssetsOutputSchema, advertisedWithFailure, refineProjectToolOutputSchema, saveProjectToolOutputSchema, readLookToolOutputSchema, readProjectToolOutputSchema, saveLookToolOutputSchema, holdLookToolOutputSchema, releaseLookToolOutputSchema, requestScreenshotUploadToolOutputSchema, importScreenshotToolOutputSchema, deleteAssetsToolOutputSchema;
|
|
3676
|
+
var projectSummary, storedAssetOutput, panelDeliveryOutput, panelOutput, lookHistoryAccess, noteField, creditsField, layoutFindingOutput, layoutAdvisoryOutput, contractField, disclosureField, operationStateOutputSchema, durableRenderFinalOutputSchema, durableBundleFinalOutputSchema, renderStripOutputSchema, optionalShape, operationStateShape, renderStripToolOutputSchema, emitBundleOutputSchema, emitBundleToolOutputSchema, appListingOutput, saveProjectOutputSchema, layoutFieldDiff, layoutReportShape, layoutReportOutput, readLookOutputSchema, storedScreenshots, searchAppListingOutputSchema, readProjectOutputSchema, agentOutcomeOutput, refineProjectOutputSchema, renderProjectOutputSchema, renderProjectToolOutputSchema, productionOperationOutputSchema, saveLookOutputSchema, holdLookOutputSchema, releaseLookOutputSchema, describeLookOutputSchema, accountCreditBalanceSchema, accountStatusOutputSchema, requestScreenshotUploadOutputSchema, importedScreenshotOutput, importScreenshotOutputSchema, deleteAssetsOutputSchema, advertisedWithFailure, refineProjectToolOutputSchema, saveProjectToolOutputSchema, readLookToolOutputSchema, readProjectToolOutputSchema, searchAppListingToolOutputSchema, saveLookToolOutputSchema, holdLookToolOutputSchema, releaseLookToolOutputSchema, requestScreenshotUploadToolOutputSchema, importScreenshotToolOutputSchema, deleteAssetsToolOutputSchema;
|
|
3583
3677
|
var init_outputSchemas = __esm({
|
|
3584
3678
|
"src/contracts/outputSchemas.ts"() {
|
|
3585
3679
|
"use strict";
|
|
@@ -3758,6 +3852,20 @@ var init_outputSchemas = __esm({
|
|
|
3758
3852
|
zipRef: z4.string().optional(),
|
|
3759
3853
|
ok: z4.boolean()
|
|
3760
3854
|
}).passthrough();
|
|
3855
|
+
appListingOutput = z4.object({
|
|
3856
|
+
appId: z4.string().describe("Apple\u2019s numeric app ID, as a string. Pass it as save_project\u2019s `appListing`."),
|
|
3857
|
+
bundleId: z4.string(),
|
|
3858
|
+
name: z4.string(),
|
|
3859
|
+
seller: z4.string(),
|
|
3860
|
+
storeUrl: z4.string().optional().describe("The listing\u2019s own App Store page. Carries the storefront, so it is the reference to reuse for a non-US app."),
|
|
3861
|
+
iconUrl: z4.string().optional().describe("The app icon on Apple\u2019s CDN. Shown as project chrome; never downloaded and never rendered into a strip."),
|
|
3862
|
+
locales: z4.array(z4.string()).describe("The App Store Connect locales this listing\u2019s languages map to, in catalog order."),
|
|
3863
|
+
baseLocale: z4.string().describe("The caption base the other locales inherit from. Derived \u2014 Apple never says which language is primary."),
|
|
3864
|
+
outputs: z4.array(z4.string()).describe("The ShotOps output devices this listing fills. iPhone only, and EMPTY for an iPad-only app."),
|
|
3865
|
+
supportsIpad: z4.boolean().describe("The listing supports iPad. Recorded, and applied to no output until iPad panels ship."),
|
|
3866
|
+
storefront: z4.string().describe("The two-letter storefront this was read from. It decides `baseLocale`."),
|
|
3867
|
+
droppedLanguageCodes: z4.array(z4.string()).optional().describe("Apple language codes this build maps no App Store Connect locale for. Omitted when nothing was dropped.")
|
|
3868
|
+
}).passthrough();
|
|
3761
3869
|
saveProjectOutputSchema = z4.object({
|
|
3762
3870
|
ok: z4.boolean(),
|
|
3763
3871
|
// Direct authenticated saves return projectId. Unsigned local saves instead return an
|
|
@@ -3776,6 +3884,10 @@ var init_outputSchemas = __esm({
|
|
|
3776
3884
|
// response stays byte-identical, and a declared-but-absent field never fails the SDK's
|
|
3777
3885
|
// structuredContent validation (the #196 lesson — an output field is never made required).
|
|
3778
3886
|
outputs: z4.array(z4.string()).optional(),
|
|
3887
|
+
// #1004 — the App Store app this save linked the project to, echoed so the caller can see the
|
|
3888
|
+
// identity it now carries without a second read. Emitted ONLY when the call passed an
|
|
3889
|
+
// `appListing` reference; a save that did not link one answers exactly as it did before.
|
|
3890
|
+
linkedApp: appListingOutput.optional(),
|
|
3779
3891
|
// #224 — save_project used to DROP this entirely: it destructured `{ look, style }` out of
|
|
3780
3892
|
// resolveLook and never forwarded the note, on the one tool where a wrong composition is
|
|
3781
3893
|
// PERSISTED rather than merely rendered.
|
|
@@ -3835,13 +3947,21 @@ var init_outputSchemas = __esm({
|
|
|
3835
3947
|
"One row per shot \xD7 target-family \xD7 locale. `resolvedAt` names the manifest cell used; `inherited` says which requested axes fell back. This is the authoritative coverage report."
|
|
3836
3948
|
)
|
|
3837
3949
|
}).passthrough().optional();
|
|
3950
|
+
searchAppListingOutputSchema = z4.object({
|
|
3951
|
+
ok: z4.boolean(),
|
|
3952
|
+
results: z4.array(appListingOutput),
|
|
3953
|
+
message: z4.string()
|
|
3954
|
+
}).passthrough();
|
|
3838
3955
|
readProjectOutputSchema = z4.object({
|
|
3839
3956
|
saved: z4.boolean(),
|
|
3840
3957
|
project: projectSummary.optional(),
|
|
3841
3958
|
message: z4.string().optional(),
|
|
3842
3959
|
layout: layoutReportOutput,
|
|
3843
3960
|
screenshots: storedScreenshots,
|
|
3844
|
-
history: lookHistoryAccess
|
|
3961
|
+
history: lookHistoryAccess,
|
|
3962
|
+
// #1004 — the App Store app this project is linked to. Omitted entirely for a project that
|
|
3963
|
+
// carries none, which is every project created without one.
|
|
3964
|
+
linkedApp: appListingOutput.optional()
|
|
3845
3965
|
}).passthrough();
|
|
3846
3966
|
agentOutcomeOutput = z4.object({
|
|
3847
3967
|
status: z4.enum([
|
|
@@ -4063,6 +4183,7 @@ var init_outputSchemas = __esm({
|
|
|
4063
4183
|
saveProjectToolOutputSchema = advertisedWithFailure(saveProjectOutputSchema);
|
|
4064
4184
|
readLookToolOutputSchema = advertisedWithFailure(readLookOutputSchema);
|
|
4065
4185
|
readProjectToolOutputSchema = advertisedWithFailure(readProjectOutputSchema);
|
|
4186
|
+
searchAppListingToolOutputSchema = advertisedWithFailure(searchAppListingOutputSchema);
|
|
4066
4187
|
saveLookToolOutputSchema = advertisedWithFailure(saveLookOutputSchema);
|
|
4067
4188
|
holdLookToolOutputSchema = advertisedWithFailure(holdLookOutputSchema);
|
|
4068
4189
|
releaseLookToolOutputSchema = advertisedWithFailure(releaseLookOutputSchema);
|
|
@@ -4074,7 +4195,7 @@ var init_outputSchemas = __esm({
|
|
|
4074
4195
|
|
|
4075
4196
|
// src/contracts/schemas.ts
|
|
4076
4197
|
import { z as z5 } from "zod";
|
|
4077
|
-
var nameField, inlineBase64Entry, refEntry, urlEntry, pathEntry, singleScreenshotEntry, localeScreenshotEntry, screenshotEntry, screenshotsDescription, slot, flatScreenshots, groupedScreenshots, screenshots, screenshotsForSaveDescription, screenshotsForSave, screenshotsOptionalDescription, screenshotsOptional, panelRef, pathPanel, panelEntry, localePanelRef, panelEntryUnion, panelsDescription, panelRefs, output, outputDeviceArray, outputDeviceList, outputsDescription, outputsField, projectOutputsDescription, projectOutputsField, clip, preview, idempotencyKey, panelPresetId, useSavedLook, locale, localesField, project, version, createProjectFlag, projectName, sourceDir, waiverShape, waiversField, renderStripShape, emitBundleShape, saveProjectShape, readLookShape, readProjectShape, refineProjectFocus, refineProjectShape, projectRefEntry, projectUrlEntry, projectPathEntry, projectScreenshotEntry, screenshotFileListDescription, screenshotFileList, renderProjectShape, productionOperationShape, SAVE_LOOK_LOOK_DESCRIPTION, saveLookShape, heldVersion, holdLookShape, releaseLookShape, describeLookShape, accountStatusShape, requestScreenshotUploadShape, deleteAssetsShape, FILE_URL_DESCRIPTION, FILE_ID_DESCRIPTION, FILE_MIME_DESCRIPTION, FILE_NAME_DESCRIPTION, FILE_DESCRIPTION, chatGptFile, importSourceName, importUrlSource, importFileSource, importPathSource, importSourceEntry, importScreenshotsDescription, importScreenshotShape, advertisedChatGptFile, advertisedOutputsField, advertisedProjectOutputsField, renderStripAdvertisedShape, emitBundleAdvertisedShape, saveProjectAdvertisedShape, saveLookAdvertisedShape, renderProjectAdvertisedShape, importScreenshotAdvertisedShape, screenshotSlotGuide, screenshotFileGuide, panelGuide, chatGptFileGuide, importSourceGuide, outputDevicesGuide, TOOL_INPUT_GUIDES;
|
|
4198
|
+
var nameField, inlineBase64Entry, refEntry, urlEntry, pathEntry, singleScreenshotEntry, localeScreenshotEntry, screenshotEntry, screenshotsDescription, slot, flatScreenshots, groupedScreenshots, screenshots, screenshotsForSaveDescription, screenshotsForSave, screenshotsOptionalDescription, screenshotsOptional, panelRef, pathPanel, panelEntry, localePanelRef, panelEntryUnion, panelsDescription, panelRefs, output, outputDeviceArray, outputDeviceList, outputsDescription, outputsField, projectOutputsDescription, projectOutputsField, clip, preview, idempotencyKey, panelPresetId, useSavedLook, locale, localesField, project, version, createProjectFlag, projectName, sourceDir, waiverShape, waiversField, renderStripShape, emitBundleShape, appListingReference, saveProjectShape, searchAppListingShape, readLookShape, readProjectShape, refineProjectFocus, refineProjectShape, projectRefEntry, projectUrlEntry, projectPathEntry, projectScreenshotEntry, screenshotFileListDescription, screenshotFileList, renderProjectShape, productionOperationShape, SAVE_LOOK_LOOK_DESCRIPTION, saveLookShape, heldVersion, holdLookShape, releaseLookShape, describeLookShape, accountStatusShape, requestScreenshotUploadShape, deleteAssetsShape, FILE_URL_DESCRIPTION, FILE_ID_DESCRIPTION, FILE_MIME_DESCRIPTION, FILE_NAME_DESCRIPTION, FILE_DESCRIPTION, chatGptFile, importSourceName, importUrlSource, importFileSource, importPathSource, importSourceEntry, importScreenshotsDescription, importScreenshotShape, advertisedChatGptFile, advertisedOutputsField, advertisedProjectOutputsField, renderStripAdvertisedShape, emitBundleAdvertisedShape, saveProjectAdvertisedShape, saveLookAdvertisedShape, renderProjectAdvertisedShape, importScreenshotAdvertisedShape, screenshotSlotGuide, screenshotFileGuide, panelGuide, chatGptFileGuide, importSourceGuide, outputDevicesGuide, TOOL_INPUT_GUIDES;
|
|
4078
4199
|
var init_schemas = __esm({
|
|
4079
4200
|
"src/contracts/schemas.ts"() {
|
|
4080
4201
|
"use strict";
|
|
@@ -4140,9 +4261,9 @@ var init_schemas = __esm({
|
|
|
4140
4261
|
seen.add(id);
|
|
4141
4262
|
});
|
|
4142
4263
|
});
|
|
4143
|
-
outputsDescription = "Target device outputs to render into one screenshot bundle. Supported now: iphone-6-9 (
|
|
4264
|
+
outputsDescription = "Target device outputs to render into one screenshot bundle. Supported now: iphone-6-9 (1320\xD72868 / APP_IPHONE_67) and iphone-6-5 (1242\xD72688 / APP_IPHONE_65). Each is rendered from the same Look and inputs; omit this field to keep the legacy single panelPresetId form unchanged. With a `project` and no list, the project's OWN saved outputs are used. Not supported with pre-rendered `panels` refs.";
|
|
4144
4265
|
outputsField = outputDeviceList.optional().describe(outputsDescription);
|
|
4145
|
-
projectOutputsDescription = "The target DEVICES this project is for \u2014 iphone-6-9 (
|
|
4266
|
+
projectOutputsDescription = "The target DEVICES this project is for \u2014 iphone-6-9 (1320\xD72868) and/or iphone-6-5 (1242\xD72688), in the order they should be offered. On save_project this is saved ON the project (the same Output list the web app shows) and the first entry becomes the previewed size unless `panelPresetId` says otherwise; on render_project it OVERRIDES the saved set for this call. Omit it and the project's saved outputs are used unchanged.";
|
|
4146
4267
|
projectOutputsField = outputDeviceList.optional().describe(projectOutputsDescription);
|
|
4147
4268
|
clip = z5.enum(["strip", "panel"]).optional().describe(MCP_TOP_LEVEL_CLIP_PRECEDENCE);
|
|
4148
4269
|
preview = z5.boolean().optional().describe(
|
|
@@ -4151,7 +4272,7 @@ var init_schemas = __esm({
|
|
|
4151
4272
|
idempotencyKey = z5.string().min(1).max(200).optional().describe(
|
|
4152
4273
|
"Stable retry key for paid Hosted production. Reuse it only for the identical request; a new key deliberately requests a new render. Omit it for deterministic fingerprint retry safety."
|
|
4153
4274
|
);
|
|
4154
|
-
panelPresetId = z5.enum(RENDERABLE_PANEL_PRESET_IDS).optional().describe("App Store screenshot size. Default r69 (6.9\u2033 iPhone,
|
|
4275
|
+
panelPresetId = z5.enum(RENDERABLE_PANEL_PRESET_IDS).optional().describe("App Store screenshot size. Default r69 (6.9\u2033 iPhone, 1320\xD72868). iPhone sizes only \u2014 iPad is not rendered yet.");
|
|
4155
4276
|
useSavedLook = z5.boolean().optional().describe("If true, style the strip with the project's saved look (ignored when `look`/`style` is given).");
|
|
4156
4277
|
locale = z5.string().optional().describe(
|
|
4157
4278
|
'App Store locale, e.g. "de-DE" (default en-US). Labels the render, selects which { "locales": \u2026 } screenshot variants render, and, for emit_bundle, selects the fastlane screenshots folder. Does not select caption text \u2014 pass the copy for this locale yourself via `style.captions[].text` (see the README\'s `captions.<locale>.json` convention).'
|
|
@@ -4228,6 +4349,9 @@ var init_schemas = __esm({
|
|
|
4228
4349
|
waivers: waiversField,
|
|
4229
4350
|
idempotencyKey
|
|
4230
4351
|
};
|
|
4352
|
+
appListingReference = z5.string().trim().min(1).max(500).optional().describe(
|
|
4353
|
+
'Link this project to its App Store listing: an App Store URL ("https://apps.apple.com/us/app/duolingo/id570060128"), a numeric app ID ("570060128"), or a bundle ID ("com.duolingo.DuolingoMobile"). The server looks the app up and the created project takes its name, bundle ID, languages and iPhone device set. Anything YOU state wins: an explicit `projectName`, `outputs` or `locale` is applied as given and the app is still linked. On an EXISTING `project` it re-links that project and rewrites nothing else \u2014 the language list, the frames and every caption stay exactly as the designer left them. Use the full store URL when the app is listed outside the US storefront; a bare ID is looked up in the US. Needs an account \u2014 an unsigned local server refuses it. Not the same field as `bundleId`, which only pre-fills a fastlane Deliverfile.'
|
|
4354
|
+
);
|
|
4231
4355
|
saveProjectShape = {
|
|
4232
4356
|
screenshots: screenshotsForSave,
|
|
4233
4357
|
panelPresetId,
|
|
@@ -4241,8 +4365,17 @@ var init_schemas = __esm({
|
|
|
4241
4365
|
version,
|
|
4242
4366
|
locale,
|
|
4243
4367
|
projectName,
|
|
4368
|
+
appListing: appListingReference,
|
|
4244
4369
|
sourceDir
|
|
4245
4370
|
};
|
|
4371
|
+
searchAppListingShape = {
|
|
4372
|
+
term: z5.string().trim().min(1).max(200).describe(
|
|
4373
|
+
"What to search the App Store for \u2014 the app name as the user said it (\"Duolingo\"), or the developer's name. Each result carries the app ID to pass as save_project's `appListing`."
|
|
4374
|
+
),
|
|
4375
|
+
country: z5.string().regex(/^[A-Za-z]{2}$/).optional().describe(
|
|
4376
|
+
`The two-letter App Store storefront to search, e.g. "dk". Defaults to "us". An app released in one country only is invisible to a search of any other, so pass the user's own storefront when they say the app is not showing up.`
|
|
4377
|
+
)
|
|
4378
|
+
};
|
|
4246
4379
|
readLookShape = { project };
|
|
4247
4380
|
readProjectShape = { project };
|
|
4248
4381
|
refineProjectFocus = z5.discriminatedUnion("kind", [
|
|
@@ -5195,6 +5328,7 @@ var init_toolRegistry = __esm({
|
|
|
5195
5328
|
"save_project",
|
|
5196
5329
|
"read_look",
|
|
5197
5330
|
"read_project",
|
|
5331
|
+
"search_app_listing",
|
|
5198
5332
|
"render_project",
|
|
5199
5333
|
"refine_project",
|
|
5200
5334
|
"save_look",
|
|
@@ -5213,6 +5347,7 @@ var init_toolRegistry = __esm({
|
|
|
5213
5347
|
save_project: "mutate",
|
|
5214
5348
|
read_look: "read",
|
|
5215
5349
|
read_project: "read",
|
|
5350
|
+
search_app_listing: "read",
|
|
5216
5351
|
render_project: "render",
|
|
5217
5352
|
refine_project: "mutate",
|
|
5218
5353
|
save_look: "mutate",
|
|
@@ -5471,6 +5606,11 @@ function operationSupported(deps, tool, account) {
|
|
|
5471
5606
|
return deps.deleteAssets ? true : "hosted_connection";
|
|
5472
5607
|
case "refine_project":
|
|
5473
5608
|
return deps.refineProject ? true : "account";
|
|
5609
|
+
// #1004 — Apple is reached through the control plane on both doors, and an unsigned caller has
|
|
5610
|
+
// no door there. Derived from the bound dep AND the account, so an anonymous hosted connection
|
|
5611
|
+
// is told 'account' rather than being promised a search its own security scheme refuses.
|
|
5612
|
+
case "search_app_listing":
|
|
5613
|
+
return deps.searchAppListing && account ? true : "account";
|
|
5474
5614
|
case "save_project":
|
|
5475
5615
|
return local || account ? true : "account";
|
|
5476
5616
|
default:
|
|
@@ -5528,6 +5668,108 @@ var init_toolsAccount = __esm({
|
|
|
5528
5668
|
}
|
|
5529
5669
|
});
|
|
5530
5670
|
|
|
5671
|
+
// ../mockup-engine/appStoreLocales.ts
|
|
5672
|
+
function appStoreLocale(code) {
|
|
5673
|
+
return BY_CODE[code];
|
|
5674
|
+
}
|
|
5675
|
+
var APP_STORE_LOCALE_CATALOG, APP_STORE_LOCALES, BY_CODE;
|
|
5676
|
+
var init_appStoreLocales = __esm({
|
|
5677
|
+
"../mockup-engine/appStoreLocales.ts"() {
|
|
5678
|
+
"use strict";
|
|
5679
|
+
APP_STORE_LOCALE_CATALOG = [
|
|
5680
|
+
{ code: "ar-SA", name: "Arabic", script: "rtl", dir: "rtl" },
|
|
5681
|
+
{ code: "bn-BD", name: "Bengali", script: "indic", dir: "ltr" },
|
|
5682
|
+
{ code: "ca", name: "Catalan", script: "latin", dir: "ltr" },
|
|
5683
|
+
{ code: "cs", name: "Czech", script: "latin", dir: "ltr" },
|
|
5684
|
+
{ code: "da", name: "Danish", script: "latin", dir: "ltr" },
|
|
5685
|
+
{ code: "de-DE", name: "German", script: "latin", dir: "ltr" },
|
|
5686
|
+
{ code: "el", name: "Greek", script: "greek", dir: "ltr" },
|
|
5687
|
+
{ code: "en-AU", name: "English (Australia)", script: "latin", dir: "ltr" },
|
|
5688
|
+
{ code: "en-CA", name: "English (Canada)", script: "latin", dir: "ltr" },
|
|
5689
|
+
{ code: "en-GB", name: "English (U.K.)", script: "latin", dir: "ltr" },
|
|
5690
|
+
{ code: "en-US", name: "English (U.S.)", script: "latin", dir: "ltr" },
|
|
5691
|
+
{ code: "es-ES", name: "Spanish (Spain)", script: "latin", dir: "ltr" },
|
|
5692
|
+
{ code: "es-MX", name: "Spanish (Mexico)", script: "latin", dir: "ltr" },
|
|
5693
|
+
{ code: "fi", name: "Finnish", script: "latin", dir: "ltr" },
|
|
5694
|
+
{ code: "fr-CA", name: "French (Canada)", script: "latin", dir: "ltr" },
|
|
5695
|
+
{ code: "fr-FR", name: "French", script: "latin", dir: "ltr" },
|
|
5696
|
+
{ code: "gu-IN", name: "Gujarati", script: "indic", dir: "ltr" },
|
|
5697
|
+
{ code: "he", name: "Hebrew", script: "rtl", dir: "rtl" },
|
|
5698
|
+
{ code: "hi", name: "Hindi", script: "indic", dir: "ltr" },
|
|
5699
|
+
{ code: "hr", name: "Croatian", script: "latin", dir: "ltr" },
|
|
5700
|
+
{ code: "hu", name: "Hungarian", script: "latin", dir: "ltr" },
|
|
5701
|
+
{ code: "id", name: "Indonesian", script: "latin", dir: "ltr" },
|
|
5702
|
+
{ code: "it", name: "Italian", script: "latin", dir: "ltr" },
|
|
5703
|
+
{ code: "ja", name: "Japanese", script: "cjk", dir: "ltr" },
|
|
5704
|
+
{ code: "kn-IN", name: "Kannada", script: "indic", dir: "ltr" },
|
|
5705
|
+
{ code: "ko", name: "Korean", script: "cjk", dir: "ltr" },
|
|
5706
|
+
{ code: "ml-IN", name: "Malayalam", script: "indic", dir: "ltr" },
|
|
5707
|
+
{ code: "mr-IN", name: "Marathi", script: "indic", dir: "ltr" },
|
|
5708
|
+
{ code: "ms", name: "Malay", script: "latin", dir: "ltr" },
|
|
5709
|
+
{ code: "nl-NL", name: "Dutch", script: "latin", dir: "ltr" },
|
|
5710
|
+
{ code: "no", name: "Norwegian", script: "latin", dir: "ltr" },
|
|
5711
|
+
{ code: "or-IN", name: "Odia", script: "indic", dir: "ltr" },
|
|
5712
|
+
{ code: "pa-IN", name: "Punjabi", script: "indic", dir: "ltr" },
|
|
5713
|
+
{ code: "pl", name: "Polish", script: "latin", dir: "ltr" },
|
|
5714
|
+
{ code: "pt-BR", name: "Portuguese (Brazil)", script: "latin", dir: "ltr" },
|
|
5715
|
+
{ code: "pt-PT", name: "Portuguese (Portugal)", script: "latin", dir: "ltr" },
|
|
5716
|
+
{ code: "ro", name: "Romanian", script: "latin", dir: "ltr" },
|
|
5717
|
+
{ code: "ru", name: "Russian", script: "cyrillic", dir: "ltr" },
|
|
5718
|
+
{ code: "sk", name: "Slovak", script: "latin", dir: "ltr" },
|
|
5719
|
+
{ code: "sl-SI", name: "Slovenian", script: "latin", dir: "ltr" },
|
|
5720
|
+
{ code: "sv", name: "Swedish", script: "latin", dir: "ltr" },
|
|
5721
|
+
{ code: "ta-IN", name: "Tamil", script: "indic", dir: "ltr" },
|
|
5722
|
+
{ code: "te-IN", name: "Telugu", script: "indic", dir: "ltr" },
|
|
5723
|
+
{ code: "th", name: "Thai", script: "thai", dir: "ltr" },
|
|
5724
|
+
{ code: "tr", name: "Turkish", script: "latin", dir: "ltr" },
|
|
5725
|
+
{ code: "uk", name: "Ukrainian", script: "cyrillic", dir: "ltr" },
|
|
5726
|
+
{ code: "ur-PK", name: "Urdu", script: "rtl", dir: "rtl" },
|
|
5727
|
+
{ code: "vi", name: "Vietnamese", script: "latin", dir: "ltr" },
|
|
5728
|
+
{ code: "zh-Hans", name: "Chinese (Simplified)", script: "cjk", dir: "ltr" },
|
|
5729
|
+
{ code: "zh-Hant", name: "Chinese (Traditional)", script: "cjk", dir: "ltr" }
|
|
5730
|
+
];
|
|
5731
|
+
APP_STORE_LOCALES = APP_STORE_LOCALE_CATALOG.map((l) => l.code);
|
|
5732
|
+
BY_CODE = Object.fromEntries(
|
|
5733
|
+
APP_STORE_LOCALE_CATALOG.map((l) => [l.code, l])
|
|
5734
|
+
);
|
|
5735
|
+
}
|
|
5736
|
+
});
|
|
5737
|
+
|
|
5738
|
+
// ../mockup-engine/appstore/appListing.ts
|
|
5739
|
+
function readAppListing(envelope) {
|
|
5740
|
+
if (envelope == null || typeof envelope !== "object" || Array.isArray(envelope)) return null;
|
|
5741
|
+
const stored = envelope[APP_LISTING_ENVELOPE_KEY];
|
|
5742
|
+
if (stored == null || typeof stored !== "object" || Array.isArray(stored)) return null;
|
|
5743
|
+
const value = stored;
|
|
5744
|
+
for (const key of ["trackId", "bundleId", "trackName", "sellerName", "storefront", "fetchedAt"]) {
|
|
5745
|
+
if (typeof value[key] !== "string") return null;
|
|
5746
|
+
}
|
|
5747
|
+
if (!Array.isArray(value.languageCodesISO2A) || !Array.isArray(value.supportedDevices)) return null;
|
|
5748
|
+
if (value.applied == null || typeof value.applied !== "object" || Array.isArray(value.applied)) return null;
|
|
5749
|
+
const applied = value.applied;
|
|
5750
|
+
if (typeof applied.projectName !== "string" || typeof applied.bundleId !== "string") return null;
|
|
5751
|
+
if (typeof applied.baseLocale !== "string" || typeof applied.supportsIpad !== "boolean") return null;
|
|
5752
|
+
for (const key of ["locales", "outputs", "droppedLanguageCodes"]) {
|
|
5753
|
+
if (!Array.isArray(applied[key])) return null;
|
|
5754
|
+
}
|
|
5755
|
+
return stored;
|
|
5756
|
+
}
|
|
5757
|
+
function appListingRewrite(listing2) {
|
|
5758
|
+
return { [APP_LISTING_ENVELOPE_KEY]: listing2 ?? void 0 };
|
|
5759
|
+
}
|
|
5760
|
+
var CATALOG_ORDER, APP_LISTING_ENVELOPE_KEY;
|
|
5761
|
+
var init_appListing = __esm({
|
|
5762
|
+
"../mockup-engine/appstore/appListing.ts"() {
|
|
5763
|
+
"use strict";
|
|
5764
|
+
init_appStoreLocales();
|
|
5765
|
+
init_devices();
|
|
5766
|
+
CATALOG_ORDER = Object.fromEntries(
|
|
5767
|
+
APP_STORE_LOCALE_CATALOG.map((locale2, index) => [locale2.code, index])
|
|
5768
|
+
);
|
|
5769
|
+
APP_LISTING_ENVELOPE_KEY = "appListing";
|
|
5770
|
+
}
|
|
5771
|
+
});
|
|
5772
|
+
|
|
5531
5773
|
// ../mockup-engine/fastlaneBundle.ts
|
|
5532
5774
|
import { strToU8, zipSync } from "fflate";
|
|
5533
5775
|
function render(template, vars) {
|
|
@@ -5850,8 +6092,17 @@ function canonicalPersistence(look2) {
|
|
|
5850
6092
|
}
|
|
5851
6093
|
return canonical;
|
|
5852
6094
|
}
|
|
6095
|
+
function upgradeLegacyShotLook(input) {
|
|
6096
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || !("phoneHeight" in input)) return input;
|
|
6097
|
+
const { phoneHeight, ...rest } = input;
|
|
6098
|
+
if (rest.deviceSize === void 0) {
|
|
6099
|
+
const parsed = typeof phoneHeight === "number" ? phoneHeight : Number.parseFloat(String(phoneHeight ?? ""));
|
|
6100
|
+
if (Number.isFinite(parsed)) rest.deviceSize = String(deviceSizeFromLegacyPhoneHeight(parsed));
|
|
6101
|
+
}
|
|
6102
|
+
return rest;
|
|
6103
|
+
}
|
|
5853
6104
|
function normalizeShotLook(input) {
|
|
5854
|
-
return canonicalPersistence({ ...DEFAULT_LOOK, ...input });
|
|
6105
|
+
return canonicalPersistence({ ...DEFAULT_LOOK, ...upgradeLegacyShotLook(input) });
|
|
5855
6106
|
}
|
|
5856
6107
|
function serializeShotLook(input) {
|
|
5857
6108
|
return canonicalPersistence(normalizeShotLook(input));
|
|
@@ -5862,7 +6113,7 @@ function comparableShotLook(input) {
|
|
|
5862
6113
|
function shotLookNumber(field, raw) {
|
|
5863
6114
|
const { min, max } = SHOT_LOOK_RANGES[field];
|
|
5864
6115
|
const parsed = typeof raw === "number" ? raw : Number.parseFloat(raw ?? "");
|
|
5865
|
-
const value = Number.isFinite(parsed) ? parsed : Number.parseFloat(
|
|
6116
|
+
const value = Number.isFinite(parsed) ? parsed : Number.parseFloat(String(SHOT_LOOK_DOMAIN[field].defaultValue));
|
|
5866
6117
|
return Math.max(min, Math.min(max, value));
|
|
5867
6118
|
}
|
|
5868
6119
|
var init_shotLook = __esm({
|
|
@@ -5916,6 +6167,158 @@ var init_panel = __esm({
|
|
|
5916
6167
|
}
|
|
5917
6168
|
});
|
|
5918
6169
|
|
|
6170
|
+
// ../mockup-engine/appstore/fingerprint.ts
|
|
6171
|
+
function isScreenshotFingerprint(value) {
|
|
6172
|
+
return typeof value === "string" && SCREENSHOT_FINGERPRINT.test(value);
|
|
6173
|
+
}
|
|
6174
|
+
async function screenshotFingerprint(bytes) {
|
|
6175
|
+
const subtle = globalThis.crypto?.subtle;
|
|
6176
|
+
if (subtle) return hex(new Uint8Array(await subtle.digest("SHA-256", bytes)));
|
|
6177
|
+
return sha256(bytes);
|
|
6178
|
+
}
|
|
6179
|
+
function sha256(input) {
|
|
6180
|
+
const length = input.length;
|
|
6181
|
+
const padded = new Uint8Array(length + 72 >> 6 << 6);
|
|
6182
|
+
padded.set(input);
|
|
6183
|
+
padded[length] = 128;
|
|
6184
|
+
const bits = length * 8;
|
|
6185
|
+
const view = new DataView(padded.buffer);
|
|
6186
|
+
view.setUint32(padded.length - 8, Math.floor(bits / 4294967296));
|
|
6187
|
+
view.setUint32(padded.length - 4, bits >>> 0);
|
|
6188
|
+
const h = new Uint32Array([
|
|
6189
|
+
1779033703,
|
|
6190
|
+
3144134277,
|
|
6191
|
+
1013904242,
|
|
6192
|
+
2773480762,
|
|
6193
|
+
1359893119,
|
|
6194
|
+
2600822924,
|
|
6195
|
+
528734635,
|
|
6196
|
+
1541459225
|
|
6197
|
+
]);
|
|
6198
|
+
const w = new Uint32Array(64);
|
|
6199
|
+
for (let block = 0; block < padded.length; block += 64) {
|
|
6200
|
+
for (let i = 0; i < 16; i++) w[i] = view.getUint32(block + i * 4);
|
|
6201
|
+
for (let i = 16; i < 64; i++) {
|
|
6202
|
+
const a2 = w[i - 15];
|
|
6203
|
+
const b2 = w[i - 2];
|
|
6204
|
+
const s0 = rotr(a2, 7) ^ rotr(a2, 18) ^ a2 >>> 3;
|
|
6205
|
+
const s1 = rotr(b2, 17) ^ rotr(b2, 19) ^ b2 >>> 10;
|
|
6206
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
6207
|
+
}
|
|
6208
|
+
let [a, b, c, d, e, f, g, hh] = [h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]];
|
|
6209
|
+
for (let i = 0; i < 64; i++) {
|
|
6210
|
+
const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
6211
|
+
const choose = e & f ^ ~e & g;
|
|
6212
|
+
const t1 = hh + s1 + choose + K[i] + w[i] >>> 0;
|
|
6213
|
+
const s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
6214
|
+
const majority = a & b ^ a & c ^ b & c;
|
|
6215
|
+
const t2 = s0 + majority >>> 0;
|
|
6216
|
+
hh = g;
|
|
6217
|
+
g = f;
|
|
6218
|
+
f = e;
|
|
6219
|
+
e = d + t1 >>> 0;
|
|
6220
|
+
d = c;
|
|
6221
|
+
c = b;
|
|
6222
|
+
b = a;
|
|
6223
|
+
a = t1 + t2 >>> 0;
|
|
6224
|
+
}
|
|
6225
|
+
h[0] = h[0] + a >>> 0;
|
|
6226
|
+
h[1] = h[1] + b >>> 0;
|
|
6227
|
+
h[2] = h[2] + c >>> 0;
|
|
6228
|
+
h[3] = h[3] + d >>> 0;
|
|
6229
|
+
h[4] = h[4] + e >>> 0;
|
|
6230
|
+
h[5] = h[5] + f >>> 0;
|
|
6231
|
+
h[6] = h[6] + g >>> 0;
|
|
6232
|
+
h[7] = h[7] + hh >>> 0;
|
|
6233
|
+
}
|
|
6234
|
+
return hex(bigEndian(h));
|
|
6235
|
+
}
|
|
6236
|
+
function bigEndian(words) {
|
|
6237
|
+
const out = new Uint8Array(words.length * 4);
|
|
6238
|
+
const view = new DataView(out.buffer);
|
|
6239
|
+
for (let i = 0; i < words.length; i++) view.setUint32(i * 4, words[i]);
|
|
6240
|
+
return out;
|
|
6241
|
+
}
|
|
6242
|
+
var SCREENSHOT_FINGERPRINT, hex, K, rotr;
|
|
6243
|
+
var init_fingerprint = __esm({
|
|
6244
|
+
"../mockup-engine/appstore/fingerprint.ts"() {
|
|
6245
|
+
"use strict";
|
|
6246
|
+
SCREENSHOT_FINGERPRINT = /^[a-f0-9]{64}$/;
|
|
6247
|
+
hex = (bytes) => {
|
|
6248
|
+
let out = "";
|
|
6249
|
+
for (let i = 0; i < bytes.length; i++) out += bytes[i].toString(16).padStart(2, "0");
|
|
6250
|
+
return out;
|
|
6251
|
+
};
|
|
6252
|
+
K = new Uint32Array([
|
|
6253
|
+
1116352408,
|
|
6254
|
+
1899447441,
|
|
6255
|
+
3049323471,
|
|
6256
|
+
3921009573,
|
|
6257
|
+
961987163,
|
|
6258
|
+
1508970993,
|
|
6259
|
+
2453635748,
|
|
6260
|
+
2870763221,
|
|
6261
|
+
3624381080,
|
|
6262
|
+
310598401,
|
|
6263
|
+
607225278,
|
|
6264
|
+
1426881987,
|
|
6265
|
+
1925078388,
|
|
6266
|
+
2162078206,
|
|
6267
|
+
2614888103,
|
|
6268
|
+
3248222580,
|
|
6269
|
+
3835390401,
|
|
6270
|
+
4022224774,
|
|
6271
|
+
264347078,
|
|
6272
|
+
604807628,
|
|
6273
|
+
770255983,
|
|
6274
|
+
1249150122,
|
|
6275
|
+
1555081692,
|
|
6276
|
+
1996064986,
|
|
6277
|
+
2554220882,
|
|
6278
|
+
2821834349,
|
|
6279
|
+
2952996808,
|
|
6280
|
+
3210313671,
|
|
6281
|
+
3336571891,
|
|
6282
|
+
3584528711,
|
|
6283
|
+
113926993,
|
|
6284
|
+
338241895,
|
|
6285
|
+
666307205,
|
|
6286
|
+
773529912,
|
|
6287
|
+
1294757372,
|
|
6288
|
+
1396182291,
|
|
6289
|
+
1695183700,
|
|
6290
|
+
1986661051,
|
|
6291
|
+
2177026350,
|
|
6292
|
+
2456956037,
|
|
6293
|
+
2730485921,
|
|
6294
|
+
2820302411,
|
|
6295
|
+
3259730800,
|
|
6296
|
+
3345764771,
|
|
6297
|
+
3516065817,
|
|
6298
|
+
3600352804,
|
|
6299
|
+
4094571909,
|
|
6300
|
+
275423344,
|
|
6301
|
+
430227734,
|
|
6302
|
+
506948616,
|
|
6303
|
+
659060556,
|
|
6304
|
+
883997877,
|
|
6305
|
+
958139571,
|
|
6306
|
+
1322822218,
|
|
6307
|
+
1537002063,
|
|
6308
|
+
1747873779,
|
|
6309
|
+
1955562222,
|
|
6310
|
+
2024104815,
|
|
6311
|
+
2227730452,
|
|
6312
|
+
2361852424,
|
|
6313
|
+
2428436474,
|
|
6314
|
+
2756734187,
|
|
6315
|
+
3204031479,
|
|
6316
|
+
3329325298
|
|
6317
|
+
]);
|
|
6318
|
+
rotr = (value, by) => value >>> by | value << 32 - by;
|
|
6319
|
+
}
|
|
6320
|
+
});
|
|
6321
|
+
|
|
5919
6322
|
// ../mockup-engine/appstore/screenManifest.ts
|
|
5920
6323
|
function isShotManifestEntry(entry) {
|
|
5921
6324
|
return typeof entry.shotId === "string";
|
|
@@ -6019,7 +6422,7 @@ function parseList(raw, mode, baseLocale) {
|
|
|
6019
6422
|
const shotId = e.shotId === void 0 || e.shotId === null ? void 0 : e.shotId;
|
|
6020
6423
|
const slot2 = e.slot === void 0 || e.slot === null ? void 0 : e.slot;
|
|
6021
6424
|
const identified = typeof shotId === "string" && shotId.length > 0 && slot2 === void 0 || typeof slot2 === "string" && slot2.length > 0 && shotId === void 0;
|
|
6022
|
-
if (!identified || typeof e.ref !== "string" || e.ref.length === 0 || variant === null || hash !== void 0 && (
|
|
6425
|
+
if (!identified || typeof e.ref !== "string" || e.ref.length === 0 || variant === null || hash !== void 0 && !isScreenshotFingerprint(hash)) {
|
|
6023
6426
|
if (mode === "strict") return null;
|
|
6024
6427
|
continue;
|
|
6025
6428
|
}
|
|
@@ -6167,25 +6570,25 @@ function rebindManifestToShots(cells, shots) {
|
|
|
6167
6570
|
}
|
|
6168
6571
|
return { manifest, findings };
|
|
6169
6572
|
}
|
|
6170
|
-
function manifestFromNamedRefs(refs, shots) {
|
|
6573
|
+
function manifestFromNamedRefs(refs, shots, hashes = {}) {
|
|
6171
6574
|
const idsByFrameName = shotIdsByFrameName(shots);
|
|
6172
6575
|
const out = [];
|
|
6173
6576
|
for (const [name, ref] of Object.entries(refs)) {
|
|
6174
6577
|
const canonical = canonicalizeManifestRef(ref);
|
|
6578
|
+
const hash = hashes[name];
|
|
6579
|
+
const withHash = (entry) => hash === void 0 ? entry : { ...entry, hash };
|
|
6175
6580
|
const ids = idsByFrameName[name];
|
|
6176
|
-
if (ids === void 0) out.push({ slot: name, variant: {}, ref: canonical });
|
|
6177
|
-
else for (const id of ids) out.push({ shotId: id, variant: {}, ref: canonical });
|
|
6581
|
+
if (ids === void 0) out.push(withHash({ slot: name, variant: {}, ref: canonical }));
|
|
6582
|
+
else for (const id of ids) out.push(withHash({ shotId: id, variant: {}, ref: canonical }));
|
|
6178
6583
|
}
|
|
6179
6584
|
return out;
|
|
6180
6585
|
}
|
|
6181
|
-
function nonBaseCellEntries(manifest) {
|
|
6182
|
-
return manifest.filter((e) => variantKey(e.variant) !== "");
|
|
6183
|
-
}
|
|
6184
6586
|
var UPLOAD_REF_RE, UUID_RE, TRUNCATED_NAMED_UPLOAD_REF, EMPTY;
|
|
6185
6587
|
var init_screenManifest = __esm({
|
|
6186
6588
|
"../mockup-engine/appstore/screenManifest.ts"() {
|
|
6187
6589
|
"use strict";
|
|
6188
6590
|
init_variant();
|
|
6591
|
+
init_fingerprint();
|
|
6189
6592
|
UPLOAD_REF_RE = /^uploads\/([^/]+)\/([^/]+(?:\/[^/]+)*)$/;
|
|
6190
6593
|
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6191
6594
|
TRUNCATED_NAMED_UPLOAD_REF = /^uploads\/[^/]+\/[^/]+\/\d+__[A-Za-z0-9_-]+$/;
|
|
@@ -6193,6 +6596,23 @@ var init_screenManifest = __esm({
|
|
|
6193
6596
|
}
|
|
6194
6597
|
});
|
|
6195
6598
|
|
|
6599
|
+
// ../mockup-engine/appstore/screenUploadPlan.ts
|
|
6600
|
+
function planScreenUploads(current, manifest, shots) {
|
|
6601
|
+
const byCell = resolveScreenManifest(manifest, shots).byShotCell;
|
|
6602
|
+
const toUpload = [];
|
|
6603
|
+
for (const c of current) {
|
|
6604
|
+
const stored = byCell[manifestShotCellKey(c.shotId, c.variant)];
|
|
6605
|
+
if (stored === void 0 || stored.hash !== c.hash) toUpload.push(c);
|
|
6606
|
+
}
|
|
6607
|
+
return { toUpload, kept: manifest };
|
|
6608
|
+
}
|
|
6609
|
+
var init_screenUploadPlan = __esm({
|
|
6610
|
+
"../mockup-engine/appstore/screenUploadPlan.ts"() {
|
|
6611
|
+
"use strict";
|
|
6612
|
+
init_screenManifest();
|
|
6613
|
+
}
|
|
6614
|
+
});
|
|
6615
|
+
|
|
6196
6616
|
// ../mockup-engine/appstore/localeAxis.ts
|
|
6197
6617
|
function deriveLocales(captionText, explicit, baseLocale = DEFAULT_LOCALE) {
|
|
6198
6618
|
const dedupe2 = (list) => {
|
|
@@ -6252,6 +6672,17 @@ function normalizeLegacyLocales(captionText, locales) {
|
|
|
6252
6672
|
}
|
|
6253
6673
|
return { captionText: nextCaption, locales: locales?.map((c) => LEGACY_LOCALE_ALIASES[c] ?? c) };
|
|
6254
6674
|
}
|
|
6675
|
+
function projectLocaleAxis(projectFile) {
|
|
6676
|
+
const file = projectFile ?? {};
|
|
6677
|
+
const baseLocaleRaw = normalizeBaseLocale(file.baseLocale);
|
|
6678
|
+
const explicit = Array.isArray(file.locales) ? file.locales.filter((locale2) => typeof locale2 === "string" && locale2.length > 0) : void 0;
|
|
6679
|
+
const { captionText, locales } = normalizeLegacyLocales(
|
|
6680
|
+
normalizeCaptionText(file.captionText, file.captionStyles, baseLocaleRaw),
|
|
6681
|
+
explicit
|
|
6682
|
+
);
|
|
6683
|
+
const baseLocale = normalizeLegacyLocales({ [baseLocaleRaw]: {} }, [baseLocaleRaw]).locales?.[0] ?? baseLocaleRaw;
|
|
6684
|
+
return { captionText, locales: deriveLocales(captionText, locales, baseLocale), baseLocale };
|
|
6685
|
+
}
|
|
6255
6686
|
var LEGACY_LOCALE_ALIASES;
|
|
6256
6687
|
var init_localeAxis = __esm({
|
|
6257
6688
|
"../mockup-engine/appstore/localeAxis.ts"() {
|
|
@@ -6552,6 +6983,16 @@ var init_layout = __esm({
|
|
|
6552
6983
|
}
|
|
6553
6984
|
});
|
|
6554
6985
|
|
|
6986
|
+
// ../mockup-engine/appstore/framelessCard.ts
|
|
6987
|
+
var FRAMELESS_BLANK_ASPECT;
|
|
6988
|
+
var init_framelessCard = __esm({
|
|
6989
|
+
"../mockup-engine/appstore/framelessCard.ts"() {
|
|
6990
|
+
"use strict";
|
|
6991
|
+
init_shotLook();
|
|
6992
|
+
FRAMELESS_BLANK_ASPECT = 9 / 19.5;
|
|
6993
|
+
}
|
|
6994
|
+
});
|
|
6995
|
+
|
|
6555
6996
|
// ../mockup-engine/appstore/serialize.ts
|
|
6556
6997
|
function extractStripStyle(state, captionText, baseLocale = DEFAULT_LOCALE) {
|
|
6557
6998
|
const gradient = stripGradientOf(state);
|
|
@@ -6821,20 +7262,15 @@ function restoreProject(json) {
|
|
|
6821
7262
|
const panels = file.panels.map((p) => ({ id: p.id }));
|
|
6822
7263
|
reseedIdCounters(shots.map((s) => s.id), panels.map((p) => p.id));
|
|
6823
7264
|
const selectedShotId = file.selectedShotId && shots.some((s) => s.id === file.selectedShotId) ? file.selectedShotId : shots[0]?.id ?? null;
|
|
6824
|
-
const
|
|
6825
|
-
const
|
|
6826
|
-
normalizeCaptionText(file.captionText, file.captionStyles, baseLocaleRaw),
|
|
6827
|
-
file.locales
|
|
6828
|
-
);
|
|
6829
|
-
const baseLocale = normalizeLegacyLocales({ [baseLocaleRaw]: {} }, [baseLocaleRaw]).locales?.[0] ?? baseLocaleRaw;
|
|
6830
|
-
const locales = deriveLocales(captionText, normalizedLocales, baseLocale);
|
|
7265
|
+
const { captionText, locales, baseLocale } = projectLocaleAxis(file);
|
|
7266
|
+
const output2 = projectOutputAxis(file);
|
|
6831
7267
|
const captionSlotIds2 = normalizeCaptionSlotIds(file.captionSlotIds, captionText, panels.map((panel) => panel.id));
|
|
6832
7268
|
const canonicalGradient = normalizeGradient(file.gradientStops, file.gradientAngle);
|
|
6833
7269
|
return {
|
|
6834
7270
|
shots: shotsInPanelOrder(shots, panels),
|
|
6835
7271
|
panels,
|
|
6836
7272
|
selectedShotId,
|
|
6837
|
-
panelPresetId:
|
|
7273
|
+
panelPresetId: output2.panelPresetId,
|
|
6838
7274
|
bgMode: file.bgMode === "perPanel" ? "perPanel" : "gradient",
|
|
6839
7275
|
// Canonical first, and spread so an old record carries neither key rather than two undefineds.
|
|
6840
7276
|
// #556 — when it IS there, the mirror is DERIVED from it rather than copied off the record: a
|
|
@@ -6868,7 +7304,7 @@ function restoreProject(json) {
|
|
|
6868
7304
|
),
|
|
6869
7305
|
// Stored device list trusted verbatim (unknown/comingSoon ids dropped); an old record with no
|
|
6870
7306
|
// `outputs` migrates from its panelPresetId (deriveOutputs) so it loads with a coherent device.
|
|
6871
|
-
outputs:
|
|
7307
|
+
outputs: output2.outputs,
|
|
6872
7308
|
// The record carries no per-locale bytes (rule 1) — the studio re-joins them from the byte-cache
|
|
6873
7309
|
// by frameName after restore (Phase 3). {} = the pre-join shape.
|
|
6874
7310
|
localeScreens: {},
|
|
@@ -6940,7 +7376,6 @@ function serializeProject(state) {
|
|
|
6940
7376
|
var init_project = __esm({
|
|
6941
7377
|
"../mockup-engine/appstore/project.ts"() {
|
|
6942
7378
|
"use strict";
|
|
6943
|
-
init_presets();
|
|
6944
7379
|
init_devices();
|
|
6945
7380
|
init_shotLook();
|
|
6946
7381
|
init_panel();
|
|
@@ -7818,9 +8253,19 @@ function applicationOutcome(facts) {
|
|
|
7818
8253
|
}
|
|
7819
8254
|
function headlineForChanges(changed) {
|
|
7820
8255
|
const first = changed[0] ?? "Updated this project.";
|
|
8256
|
+
const whole = changed.join(" \xB7 ");
|
|
8257
|
+
if (changed.length <= 1 || whole.length <= AGENT_OUTCOME_BOUNDS.headline) {
|
|
8258
|
+
return boundOutcomeText(changed.length > 0 ? whole : first, AGENT_OUTCOME_BOUNDS.headline);
|
|
8259
|
+
}
|
|
8260
|
+
let kept = changed.length - 1;
|
|
8261
|
+
while (kept > 1) {
|
|
8262
|
+
const rest2 = changed.length - kept;
|
|
8263
|
+
const candidate = `${changed.slice(0, kept).join(" \xB7 ")} (+${rest2} more change${rest2 === 1 ? "" : "s"})`;
|
|
8264
|
+
if (candidate.length <= AGENT_OUTCOME_BOUNDS.headline) return candidate;
|
|
8265
|
+
kept -= 1;
|
|
8266
|
+
}
|
|
7821
8267
|
const rest = changed.length - 1;
|
|
7822
|
-
|
|
7823
|
-
return boundOutcomeText(headline, AGENT_OUTCOME_BOUNDS.headline);
|
|
8268
|
+
return boundOutcomeText(`${first} (+${rest} more change${rest === 1 ? "" : "s"})`, AGENT_OUTCOME_BOUNDS.headline);
|
|
7824
8269
|
}
|
|
7825
8270
|
function failureOutcome(reason, headline) {
|
|
7826
8271
|
return {
|
|
@@ -7970,7 +8415,7 @@ function lookFromAiShotPatch(current, patch, allowHOffset) {
|
|
|
7970
8415
|
}
|
|
7971
8416
|
if (patch.orbit !== void 0) next.cameraPos = posFromAzimuthDeg(normalizedAzimuth(patch.orbit));
|
|
7972
8417
|
if (patch.roll !== void 0) next.roll = String(shotLookNumber("roll", patch.roll));
|
|
7973
|
-
if (patch.
|
|
8418
|
+
if (patch.deviceSize !== void 0) next.deviceSize = String(shotLookNumber("deviceSize", patch.deviceSize));
|
|
7974
8419
|
if (patch.hOffset !== void 0 && allowHOffset) next.hOffset = String(shotLookNumber("hOffset", patch.hOffset));
|
|
7975
8420
|
if (patch.vOffset !== void 0) next.vOffset = String(shotLookNumber("vOffset", patch.vOffset));
|
|
7976
8421
|
if (patch.material !== void 0) next.material = patch.material;
|
|
@@ -7992,6 +8437,13 @@ function lookFromAiShotPatch(current, patch, allowHOffset) {
|
|
|
7992
8437
|
if (patch.reflections !== void 0) next.reflections = patch.reflections;
|
|
7993
8438
|
if (patch.clipToFrame === true) next.clipToFrame = true;
|
|
7994
8439
|
else if (patch.clipToFrame === false) delete next.clipToFrame;
|
|
8440
|
+
if (patch.deviceFrame === false) next.deviceFrame = false;
|
|
8441
|
+
else if (patch.deviceFrame === true) delete next.deviceFrame;
|
|
8442
|
+
if (patch.screenCornerRadius !== void 0) {
|
|
8443
|
+
const radius = String(shotLookNumber("screenCornerRadius", patch.screenCornerRadius));
|
|
8444
|
+
if (radius === SHOT_LOOK_DOMAIN.screenCornerRadius.defaultValue) delete next.screenCornerRadius;
|
|
8445
|
+
else next.screenCornerRadius = radius;
|
|
8446
|
+
}
|
|
7995
8447
|
return JSON.stringify(next) === JSON.stringify(current) ? current : next;
|
|
7996
8448
|
}
|
|
7997
8449
|
function resolveCaptionNumber(input, current, range) {
|
|
@@ -8041,6 +8493,7 @@ var init_refinementValues = __esm({
|
|
|
8041
8493
|
init_gradient();
|
|
8042
8494
|
init_panel();
|
|
8043
8495
|
init_refineIntent();
|
|
8496
|
+
init_defaults();
|
|
8044
8497
|
init_shotLook();
|
|
8045
8498
|
AI_SHOT_LOOK_PROJECTION = {
|
|
8046
8499
|
angle: {
|
|
@@ -8060,10 +8513,10 @@ var init_refinementValues = __esm({
|
|
|
8060
8513
|
reason: "numeric-string",
|
|
8061
8514
|
project: (look2) => ({ roll: shotLookNumber("roll", look2.roll) })
|
|
8062
8515
|
},
|
|
8063
|
-
|
|
8516
|
+
deviceSize: {
|
|
8064
8517
|
disposition: "transformed",
|
|
8065
8518
|
reason: "numeric-string",
|
|
8066
|
-
project: (look2) => ({
|
|
8519
|
+
project: (look2) => ({ deviceSize: shotLookNumber("deviceSize", look2.deviceSize) })
|
|
8067
8520
|
},
|
|
8068
8521
|
hOffset: {
|
|
8069
8522
|
disposition: "transformed",
|
|
@@ -8120,6 +8573,16 @@ var init_refinementValues = __esm({
|
|
|
8120
8573
|
reason: "false-is-absence",
|
|
8121
8574
|
project: (look2) => ({ clipToFrame: look2.clipToFrame === true })
|
|
8122
8575
|
},
|
|
8576
|
+
deviceFrame: {
|
|
8577
|
+
disposition: "transformed",
|
|
8578
|
+
reason: "true-is-default",
|
|
8579
|
+
project: (look2) => ({ deviceFrame: look2.deviceFrame !== false })
|
|
8580
|
+
},
|
|
8581
|
+
screenCornerRadius: {
|
|
8582
|
+
disposition: "transformed",
|
|
8583
|
+
reason: "numeric-string",
|
|
8584
|
+
project: (look2) => ({ screenCornerRadius: shotLookNumber("screenCornerRadius", look2.screenCornerRadius) })
|
|
8585
|
+
},
|
|
8123
8586
|
glare: {
|
|
8124
8587
|
disposition: "direct",
|
|
8125
8588
|
reason: "same-value",
|
|
@@ -8841,7 +9304,7 @@ var TEMPLATE_OWNED_SHOT_FIELDS;
|
|
|
8841
9304
|
var init_templatePoseGuard = __esm({
|
|
8842
9305
|
"../mockup-engine/appstore/templatePoseGuard.ts"() {
|
|
8843
9306
|
"use strict";
|
|
8844
|
-
TEMPLATE_OWNED_SHOT_FIELDS = ["orbit", "roll", "angle", "
|
|
9307
|
+
TEMPLATE_OWNED_SHOT_FIELDS = ["orbit", "roll", "angle", "deviceSize", "hOffset", "vOffset"];
|
|
8845
9308
|
}
|
|
8846
9309
|
});
|
|
8847
9310
|
|
|
@@ -9117,6 +9580,33 @@ function applyProjectRefinementWithAssets(state, patch, panelIds, assets = []) {
|
|
|
9117
9580
|
).patch;
|
|
9118
9581
|
}
|
|
9119
9582
|
}
|
|
9583
|
+
const layoutFrames = patch.frames?.length === positionalCount ? patch.frames : void 0;
|
|
9584
|
+
if (layoutFrames?.some((frame) => frame?.layout)) {
|
|
9585
|
+
const panelsByTemplate = {};
|
|
9586
|
+
const order = [];
|
|
9587
|
+
requestedPanelIds.forEach((panelId, index) => {
|
|
9588
|
+
const id = layoutFrames[index]?.layout;
|
|
9589
|
+
if (!id || !surviving.has(panelId)) return;
|
|
9590
|
+
if (!panelsByTemplate[id]) {
|
|
9591
|
+
panelsByTemplate[id] = [];
|
|
9592
|
+
order.push(id);
|
|
9593
|
+
}
|
|
9594
|
+
panelsByTemplate[id].push(panelId);
|
|
9595
|
+
});
|
|
9596
|
+
for (const id of order) {
|
|
9597
|
+
const panelIds2 = panelsByTemplate[id];
|
|
9598
|
+
next = applyLayout(next, id, panelIds2);
|
|
9599
|
+
if (layoutTemplateById(id)) {
|
|
9600
|
+
const shots = next.shots;
|
|
9601
|
+
patch = dropTemplatePoses(
|
|
9602
|
+
patch,
|
|
9603
|
+
new Set(panelIds2),
|
|
9604
|
+
requestedPanelIds,
|
|
9605
|
+
(deviceId) => shots.find((shot) => shot.id === deviceId)?.panelId
|
|
9606
|
+
).patch;
|
|
9607
|
+
}
|
|
9608
|
+
}
|
|
9609
|
+
}
|
|
9120
9610
|
if (patch.shots?.length === positionalCount) {
|
|
9121
9611
|
const entries = new Map(requestedPanelIds.map((panelId, index) => [panelId, patch.shots[index]]));
|
|
9122
9612
|
const shared = new Set(
|
|
@@ -9178,11 +9668,16 @@ function applyProjectRefinementWithAssets(state, patch, panelIds, assets = []) {
|
|
|
9178
9668
|
...stripBackground.floorReflection !== void 0 ? { floorReflection: stripBackground.floorReflection } : {}
|
|
9179
9669
|
};
|
|
9180
9670
|
}
|
|
9181
|
-
if (frameEntries?.some((frame) => frame.background && Object.keys(frame.background).length > 0)) {
|
|
9671
|
+
if (frameEntries?.some((frame) => frame.clearBackground || frame.background && Object.keys(frame.background).length > 0)) {
|
|
9182
9672
|
const inherited = panelBackgroundFromGradientPatch(stripGradientOf(next));
|
|
9183
9673
|
const panelBackgrounds = { ...next.panelBackgrounds };
|
|
9184
9674
|
let changed = false;
|
|
9185
9675
|
requestedPanelIds.forEach((panelId, index) => {
|
|
9676
|
+
if (frameEntries[index]?.clearBackground && surviving.has(panelId)) {
|
|
9677
|
+
delete panelBackgrounds[panelId];
|
|
9678
|
+
changed = true;
|
|
9679
|
+
return;
|
|
9680
|
+
}
|
|
9186
9681
|
const entry = frameEntries[index]?.background;
|
|
9187
9682
|
if (!entry || Object.keys(entry).length === 0 || !surviving.has(panelId)) return;
|
|
9188
9683
|
const gradient = gradientFromAiBackground(backgroundGradient(panelBackgrounds[panelId] ?? inherited), entry);
|
|
@@ -9193,7 +9688,11 @@ function applyProjectRefinementWithAssets(state, patch, panelIds, assets = []) {
|
|
|
9193
9688
|
if (changed) next = { ...next, panelBackgrounds };
|
|
9194
9689
|
}
|
|
9195
9690
|
if (patch.outputs) {
|
|
9196
|
-
const
|
|
9691
|
+
const valid = patch.outputs.filter((id, index, all) => {
|
|
9692
|
+
const device = deviceById(id);
|
|
9693
|
+
return device && !device.comingSoon && all.indexOf(id) === index;
|
|
9694
|
+
});
|
|
9695
|
+
const wanted = valid.length > 0 ? valid : next.outputs;
|
|
9197
9696
|
const outputCaptions = pruneOutputFamilies(next.outputCaptions, wanted);
|
|
9198
9697
|
const outputScreens = next.outputScreens === void 0 ? void 0 : pruneOutputFamilies(next.outputScreens, wanted);
|
|
9199
9698
|
const previewPresetId = next.previewPresetId === void 0 ? void 0 : wanted.some((id) => deviceById(id)?.panelPresetId === next.previewPresetId) ? next.previewPresetId : deviceById(wanted[0])?.panelPresetId ?? next.previewPresetId;
|
|
@@ -9259,73 +9758,6 @@ var init_refinement = __esm({
|
|
|
9259
9758
|
}
|
|
9260
9759
|
});
|
|
9261
9760
|
|
|
9262
|
-
// ../mockup-engine/appStoreLocales.ts
|
|
9263
|
-
function appStoreLocale(code) {
|
|
9264
|
-
return BY_CODE[code];
|
|
9265
|
-
}
|
|
9266
|
-
var APP_STORE_LOCALE_CATALOG, APP_STORE_LOCALES, BY_CODE;
|
|
9267
|
-
var init_appStoreLocales = __esm({
|
|
9268
|
-
"../mockup-engine/appStoreLocales.ts"() {
|
|
9269
|
-
"use strict";
|
|
9270
|
-
APP_STORE_LOCALE_CATALOG = [
|
|
9271
|
-
{ code: "ar-SA", name: "Arabic", script: "rtl", dir: "rtl" },
|
|
9272
|
-
{ code: "bn-BD", name: "Bengali", script: "indic", dir: "ltr" },
|
|
9273
|
-
{ code: "ca", name: "Catalan", script: "latin", dir: "ltr" },
|
|
9274
|
-
{ code: "cs", name: "Czech", script: "latin", dir: "ltr" },
|
|
9275
|
-
{ code: "da", name: "Danish", script: "latin", dir: "ltr" },
|
|
9276
|
-
{ code: "de-DE", name: "German", script: "latin", dir: "ltr" },
|
|
9277
|
-
{ code: "el", name: "Greek", script: "greek", dir: "ltr" },
|
|
9278
|
-
{ code: "en-AU", name: "English (Australia)", script: "latin", dir: "ltr" },
|
|
9279
|
-
{ code: "en-CA", name: "English (Canada)", script: "latin", dir: "ltr" },
|
|
9280
|
-
{ code: "en-GB", name: "English (U.K.)", script: "latin", dir: "ltr" },
|
|
9281
|
-
{ code: "en-US", name: "English (U.S.)", script: "latin", dir: "ltr" },
|
|
9282
|
-
{ code: "es-ES", name: "Spanish (Spain)", script: "latin", dir: "ltr" },
|
|
9283
|
-
{ code: "es-MX", name: "Spanish (Mexico)", script: "latin", dir: "ltr" },
|
|
9284
|
-
{ code: "fi", name: "Finnish", script: "latin", dir: "ltr" },
|
|
9285
|
-
{ code: "fr-CA", name: "French (Canada)", script: "latin", dir: "ltr" },
|
|
9286
|
-
{ code: "fr-FR", name: "French", script: "latin", dir: "ltr" },
|
|
9287
|
-
{ code: "gu-IN", name: "Gujarati", script: "indic", dir: "ltr" },
|
|
9288
|
-
{ code: "he", name: "Hebrew", script: "rtl", dir: "rtl" },
|
|
9289
|
-
{ code: "hi", name: "Hindi", script: "indic", dir: "ltr" },
|
|
9290
|
-
{ code: "hr", name: "Croatian", script: "latin", dir: "ltr" },
|
|
9291
|
-
{ code: "hu", name: "Hungarian", script: "latin", dir: "ltr" },
|
|
9292
|
-
{ code: "id", name: "Indonesian", script: "latin", dir: "ltr" },
|
|
9293
|
-
{ code: "it", name: "Italian", script: "latin", dir: "ltr" },
|
|
9294
|
-
{ code: "ja", name: "Japanese", script: "cjk", dir: "ltr" },
|
|
9295
|
-
{ code: "kn-IN", name: "Kannada", script: "indic", dir: "ltr" },
|
|
9296
|
-
{ code: "ko", name: "Korean", script: "cjk", dir: "ltr" },
|
|
9297
|
-
{ code: "ml-IN", name: "Malayalam", script: "indic", dir: "ltr" },
|
|
9298
|
-
{ code: "mr-IN", name: "Marathi", script: "indic", dir: "ltr" },
|
|
9299
|
-
{ code: "ms", name: "Malay", script: "latin", dir: "ltr" },
|
|
9300
|
-
{ code: "nl-NL", name: "Dutch", script: "latin", dir: "ltr" },
|
|
9301
|
-
{ code: "no", name: "Norwegian", script: "latin", dir: "ltr" },
|
|
9302
|
-
{ code: "or-IN", name: "Odia", script: "indic", dir: "ltr" },
|
|
9303
|
-
{ code: "pa-IN", name: "Punjabi", script: "indic", dir: "ltr" },
|
|
9304
|
-
{ code: "pl", name: "Polish", script: "latin", dir: "ltr" },
|
|
9305
|
-
{ code: "pt-BR", name: "Portuguese (Brazil)", script: "latin", dir: "ltr" },
|
|
9306
|
-
{ code: "pt-PT", name: "Portuguese (Portugal)", script: "latin", dir: "ltr" },
|
|
9307
|
-
{ code: "ro", name: "Romanian", script: "latin", dir: "ltr" },
|
|
9308
|
-
{ code: "ru", name: "Russian", script: "cyrillic", dir: "ltr" },
|
|
9309
|
-
{ code: "sk", name: "Slovak", script: "latin", dir: "ltr" },
|
|
9310
|
-
{ code: "sl-SI", name: "Slovenian", script: "latin", dir: "ltr" },
|
|
9311
|
-
{ code: "sv", name: "Swedish", script: "latin", dir: "ltr" },
|
|
9312
|
-
{ code: "ta-IN", name: "Tamil", script: "indic", dir: "ltr" },
|
|
9313
|
-
{ code: "te-IN", name: "Telugu", script: "indic", dir: "ltr" },
|
|
9314
|
-
{ code: "th", name: "Thai", script: "thai", dir: "ltr" },
|
|
9315
|
-
{ code: "tr", name: "Turkish", script: "latin", dir: "ltr" },
|
|
9316
|
-
{ code: "uk", name: "Ukrainian", script: "cyrillic", dir: "ltr" },
|
|
9317
|
-
{ code: "ur-PK", name: "Urdu", script: "rtl", dir: "rtl" },
|
|
9318
|
-
{ code: "vi", name: "Vietnamese", script: "latin", dir: "ltr" },
|
|
9319
|
-
{ code: "zh-Hans", name: "Chinese (Simplified)", script: "cjk", dir: "ltr" },
|
|
9320
|
-
{ code: "zh-Hant", name: "Chinese (Traditional)", script: "cjk", dir: "ltr" }
|
|
9321
|
-
];
|
|
9322
|
-
APP_STORE_LOCALES = APP_STORE_LOCALE_CATALOG.map((l) => l.code);
|
|
9323
|
-
BY_CODE = Object.fromEntries(
|
|
9324
|
-
APP_STORE_LOCALE_CATALOG.map((l) => [l.code, l])
|
|
9325
|
-
);
|
|
9326
|
-
}
|
|
9327
|
-
});
|
|
9328
|
-
|
|
9329
9761
|
// ../mockup-engine/appstore/agentTurnRepetition.ts
|
|
9330
9762
|
function appliedOperationOf(operation) {
|
|
9331
9763
|
if (!NON_REPEATABLE_OPS.has(operation.op)) return null;
|
|
@@ -9866,6 +10298,13 @@ var init_agentTurnConformance = __esm({
|
|
|
9866
10298
|
summary: "Restyled 1 device",
|
|
9867
10299
|
broaderChanges: [],
|
|
9868
10300
|
remainingMismatch: null
|
|
10301
|
+
},
|
|
10302
|
+
receipt: {
|
|
10303
|
+
committed: true,
|
|
10304
|
+
status: "applied",
|
|
10305
|
+
generationIds: ["generation-1", "generation-2"],
|
|
10306
|
+
recordVersion: 8,
|
|
10307
|
+
usage: { inputTokens: 20, cacheReadTokens: 4, imageTokens: null, outputTokens: 10, totalTokens: 30 }
|
|
9869
10308
|
}
|
|
9870
10309
|
}
|
|
9871
10310
|
};
|
|
@@ -9968,6 +10407,8 @@ var init_appstore = __esm({
|
|
|
9968
10407
|
init_captionModel();
|
|
9969
10408
|
init_variant();
|
|
9970
10409
|
init_screenManifest();
|
|
10410
|
+
init_fingerprint();
|
|
10411
|
+
init_screenUploadPlan();
|
|
9971
10412
|
init_productionInput();
|
|
9972
10413
|
init_productionReadiness();
|
|
9973
10414
|
init_localeAxis();
|
|
@@ -9977,6 +10418,7 @@ var init_appstore = __esm({
|
|
|
9977
10418
|
init_localeVariants();
|
|
9978
10419
|
init_shot();
|
|
9979
10420
|
init_layout();
|
|
10421
|
+
init_framelessCard();
|
|
9980
10422
|
init_serialize();
|
|
9981
10423
|
init_project();
|
|
9982
10424
|
init_look();
|
|
@@ -10283,6 +10725,70 @@ var init_lookResolution = __esm({
|
|
|
10283
10725
|
}
|
|
10284
10726
|
});
|
|
10285
10727
|
|
|
10728
|
+
// ../mockup-engine/appstore/recordEnvelope.ts
|
|
10729
|
+
function isEnvelope(value) {
|
|
10730
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
10731
|
+
}
|
|
10732
|
+
function isBlankRecord(value) {
|
|
10733
|
+
return isEnvelope(value) && value.record === 0 && value.project === void 0;
|
|
10734
|
+
}
|
|
10735
|
+
function rebuildEnvelope(stored, next) {
|
|
10736
|
+
const out = isEnvelope(stored) ? { ...stored } : {};
|
|
10737
|
+
const { project: project2, manifest, ...explicit } = next;
|
|
10738
|
+
out.record = SAVED_RECORD_VERSION;
|
|
10739
|
+
out.project = project2;
|
|
10740
|
+
if ("manifest" in next) {
|
|
10741
|
+
const folded = manifest ? mergeManifest([], manifest) : [];
|
|
10742
|
+
if (folded.length > 0) out.handoffRefs = folded;
|
|
10743
|
+
else delete out.handoffRefs;
|
|
10744
|
+
}
|
|
10745
|
+
for (const [key, value] of Object.entries(explicit)) {
|
|
10746
|
+
if (value === void 0) delete out[key];
|
|
10747
|
+
else out[key] = value;
|
|
10748
|
+
}
|
|
10749
|
+
return out;
|
|
10750
|
+
}
|
|
10751
|
+
function custodyUnion(...manifests) {
|
|
10752
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10753
|
+
const out = [];
|
|
10754
|
+
for (const manifest of manifests) {
|
|
10755
|
+
for (const entry of manifest) {
|
|
10756
|
+
if (seen.has(entry.ref)) continue;
|
|
10757
|
+
seen.add(entry.ref);
|
|
10758
|
+
out.push(entry);
|
|
10759
|
+
}
|
|
10760
|
+
}
|
|
10761
|
+
return out;
|
|
10762
|
+
}
|
|
10763
|
+
var SAVED_RECORD_VERSION, ENVELOPE_KEYS_A_SAVE_OWNS;
|
|
10764
|
+
var init_recordEnvelope = __esm({
|
|
10765
|
+
"../mockup-engine/appstore/recordEnvelope.ts"() {
|
|
10766
|
+
"use strict";
|
|
10767
|
+
init_screenManifest();
|
|
10768
|
+
SAVED_RECORD_VERSION = 2;
|
|
10769
|
+
ENVELOPE_KEYS_A_SAVE_OWNS = [
|
|
10770
|
+
"record",
|
|
10771
|
+
"project",
|
|
10772
|
+
"fastlaneBundleId",
|
|
10773
|
+
"fastlaneLocale",
|
|
10774
|
+
"lastBundleExportAt",
|
|
10775
|
+
"lastShareAt",
|
|
10776
|
+
"latestVersion",
|
|
10777
|
+
"heldVersion",
|
|
10778
|
+
"source",
|
|
10779
|
+
"baseline",
|
|
10780
|
+
"reach",
|
|
10781
|
+
"handoffRefs",
|
|
10782
|
+
// The App Store listing a project is linked to (#1001). OWNED, because owned is the only kind a
|
|
10783
|
+
// DETACH can delete — an unowned key could never be removed. That does not oblige a save to carry
|
|
10784
|
+
// it: the rule above already says an unnamed key survives. Naming it is the hazard here, so no
|
|
10785
|
+
// ordinary save path may mention `appListing` — only a deliberate attach (a value) or detach
|
|
10786
|
+
// (`undefined` through rebuildEnvelope, `null` through patchRecord).
|
|
10787
|
+
"appListing"
|
|
10788
|
+
];
|
|
10789
|
+
}
|
|
10790
|
+
});
|
|
10791
|
+
|
|
10286
10792
|
// ../api/_lib/projectRecord.ts
|
|
10287
10793
|
function parseScreenPurgeRefs(value) {
|
|
10288
10794
|
if (!Array.isArray(value)) {
|
|
@@ -10299,13 +10805,15 @@ function parseScreenPurgeRefs(value) {
|
|
|
10299
10805
|
}
|
|
10300
10806
|
return entries;
|
|
10301
10807
|
}
|
|
10302
|
-
var PROJECT_RECORD_MAX_BYTES;
|
|
10808
|
+
var PROJECT_RECORD_MAX_BYTES, WRITABLE_ENVELOPE_KEYS;
|
|
10303
10809
|
var init_projectRecord = __esm({
|
|
10304
10810
|
"../api/_lib/projectRecord.ts"() {
|
|
10305
10811
|
"use strict";
|
|
10306
10812
|
init_projectFileGuard();
|
|
10813
|
+
init_recordEnvelope();
|
|
10307
10814
|
init_screenManifest();
|
|
10308
10815
|
PROJECT_RECORD_MAX_BYTES = 2 * 1024 * 1024;
|
|
10816
|
+
WRITABLE_ENVELOPE_KEYS = new Set(ENVELOPE_KEYS_A_SAVE_OWNS);
|
|
10309
10817
|
}
|
|
10310
10818
|
});
|
|
10311
10819
|
|
|
@@ -11241,14 +11749,6 @@ function preservedCells(record8, nextProjectFile) {
|
|
|
11241
11749
|
}
|
|
11242
11750
|
return { manifest: rebound.manifest, findings };
|
|
11243
11751
|
}
|
|
11244
|
-
function screenCustodyUnion(...manifests) {
|
|
11245
|
-
const seen = /* @__PURE__ */ new Set();
|
|
11246
|
-
return manifests.flatMap((manifest) => manifest.filter((entry) => {
|
|
11247
|
-
if (seen.has(entry.ref)) return false;
|
|
11248
|
-
seen.add(entry.ref);
|
|
11249
|
-
return true;
|
|
11250
|
-
}));
|
|
11251
|
-
}
|
|
11252
11752
|
function screenCustodyFromRecord(record8) {
|
|
11253
11753
|
return parseScreenPurgeRefs(record8?.screenPurgeRefs) ?? [];
|
|
11254
11754
|
}
|
|
@@ -11256,7 +11756,27 @@ function allLiveScreenRefs(record8) {
|
|
|
11256
11756
|
const parsed = parseScreenManifest(record8?.handoffRefs);
|
|
11257
11757
|
return parsed ? allManifestEntries(parsed) : [];
|
|
11258
11758
|
}
|
|
11259
|
-
|
|
11759
|
+
function projectSaveEnvelope(stored, projectFile, manifest, source, listing2) {
|
|
11760
|
+
const custody = custodyUnion(screenCustodyFromRecord(stored), allLiveScreenRefs(stored), manifest);
|
|
11761
|
+
const storedSource = stored != null && typeof stored === "object" && !Array.isArray(stored) ? stored.source : void 0;
|
|
11762
|
+
const storedKind = storedSource != null && typeof storedSource === "object" ? storedSource.kind : void 0;
|
|
11763
|
+
const mcpOrigin = storedKind === "agent" || storedKind === "local-path";
|
|
11764
|
+
const sourceForEnvelope = storedSource != null && !mcpOrigin ? storedSource : source ?? storedSource;
|
|
11765
|
+
return rebuildEnvelope(stored, {
|
|
11766
|
+
project: projectFile,
|
|
11767
|
+
manifest,
|
|
11768
|
+
source: sourceForEnvelope ?? void 0,
|
|
11769
|
+
reach: manifest.length > 0 ? "account" : void 0,
|
|
11770
|
+
screenPurgeRefs: custody.length > 0 ? custody : void 0,
|
|
11771
|
+
// #1004 — the App Store listing, and the ONE shape in which this door may name that key.
|
|
11772
|
+
// SPREAD, never a ternary yielding `undefined`: `rebuildEnvelope` deletes on `undefined`, so
|
|
11773
|
+
// `appListing: listing ?? undefined` would wipe a stored association on every ordinary save —
|
|
11774
|
+
// which is exactly the hazard `recordEnvelope.ts` warns about. A save with no reference names
|
|
11775
|
+
// no key at all, and the stored snapshot survives because nothing touched it.
|
|
11776
|
+
...listing2 ? { ...appListingRewrite(listing2), fastlaneBundleId: listing2.applied.bundleId } : {}
|
|
11777
|
+
});
|
|
11778
|
+
}
|
|
11779
|
+
async function persistProject(deps, args, record8, claimPolicy = "none", listing2) {
|
|
11260
11780
|
const names = screenNamesFromRecord(record8);
|
|
11261
11781
|
const source = names.length === 0 ? void 0 : args.sourceDir ? { kind: "local-path", ref: { dir: args.sourceDir, names } } : { kind: "agent", ref: { names } };
|
|
11262
11782
|
const introducedRefs = handoffRefsFromScreenshots(args.screenshots, names, deps.userId);
|
|
@@ -11270,30 +11790,7 @@ async function persistProject(deps, args, record8, claimPolicy = "none") {
|
|
|
11270
11790
|
preserved.manifest,
|
|
11271
11791
|
manifestFromNamedRefs(handoffRefs, shotRefsFromProjectFile(record8))
|
|
11272
11792
|
);
|
|
11273
|
-
|
|
11274
|
-
screenCustodyFromRecord(existingRecord),
|
|
11275
|
-
allLiveScreenRefs(existingRecord),
|
|
11276
|
-
manifest
|
|
11277
|
-
);
|
|
11278
|
-
const stored = existingRecord != null && typeof existingRecord === "object" && !Array.isArray(existingRecord) ? { ...existingRecord } : {};
|
|
11279
|
-
delete stored.record;
|
|
11280
|
-
delete stored.project;
|
|
11281
|
-
delete stored.handoffRefs;
|
|
11282
|
-
delete stored.reach;
|
|
11283
|
-
delete stored.screenPurgeRefs;
|
|
11284
|
-
const storedSource = stored.source;
|
|
11285
|
-
delete stored.source;
|
|
11286
|
-
const storedKind = storedSource != null && typeof storedSource === "object" ? storedSource.kind : void 0;
|
|
11287
|
-
const mcpOrigin = storedKind === "agent" || storedKind === "local-path";
|
|
11288
|
-
const sourceForEnvelope = storedSource != null && !mcpOrigin ? storedSource : source ?? storedSource;
|
|
11289
|
-
return {
|
|
11290
|
-
...stored,
|
|
11291
|
-
record: SAVED_RECORD_VERSION,
|
|
11292
|
-
project: record8,
|
|
11293
|
-
...sourceForEnvelope != null ? { source: sourceForEnvelope } : {},
|
|
11294
|
-
...manifest.length > 0 ? { handoffRefs: manifest, reach: "account" } : {},
|
|
11295
|
-
...custody.length > 0 ? { screenPurgeRefs: custody } : {}
|
|
11296
|
-
};
|
|
11793
|
+
return projectSaveEnvelope(existingRecord, record8, manifest, source, listing2);
|
|
11297
11794
|
};
|
|
11298
11795
|
const ctx = {
|
|
11299
11796
|
actorUserId: deps.userId,
|
|
@@ -11359,7 +11856,6 @@ function projectFileFromRecord(record8) {
|
|
|
11359
11856
|
const inner = record8?.project;
|
|
11360
11857
|
return inner != null && typeof inner === "object" ? inner : record8;
|
|
11361
11858
|
}
|
|
11362
|
-
var SAVED_RECORD_VERSION;
|
|
11363
11859
|
var init_projectPersistence = __esm({
|
|
11364
11860
|
"src/project/projectPersistence.ts"() {
|
|
11365
11861
|
"use strict";
|
|
@@ -11367,12 +11863,13 @@ var init_projectPersistence = __esm({
|
|
|
11367
11863
|
init_projectRecord();
|
|
11368
11864
|
init_uploadRef();
|
|
11369
11865
|
init_screenManifest();
|
|
11866
|
+
init_recordEnvelope();
|
|
11867
|
+
init_appListing();
|
|
11370
11868
|
init_authorization();
|
|
11371
11869
|
init_projectResolution();
|
|
11372
11870
|
init_schemas();
|
|
11373
11871
|
init_screenshotInput();
|
|
11374
11872
|
init_uploads();
|
|
11375
|
-
SAVED_RECORD_VERSION = 2;
|
|
11376
11873
|
}
|
|
11377
11874
|
});
|
|
11378
11875
|
|
|
@@ -11658,7 +12155,6 @@ var init_projectAccessStore = __esm({
|
|
|
11658
12155
|
});
|
|
11659
12156
|
|
|
11660
12157
|
// ../api/_lib/projectLookStore.ts
|
|
11661
|
-
var FREE_EDITABLE_PROJECT_LIMIT;
|
|
11662
12158
|
var init_projectLookStore = __esm({
|
|
11663
12159
|
"../api/_lib/projectLookStore.ts"() {
|
|
11664
12160
|
"use strict";
|
|
@@ -11666,11 +12162,17 @@ var init_projectLookStore = __esm({
|
|
|
11666
12162
|
init_look();
|
|
11667
12163
|
init_lookDivergence();
|
|
11668
12164
|
init_projectFileGuard();
|
|
12165
|
+
init_shotLook();
|
|
11669
12166
|
init_supabaseRest();
|
|
11670
12167
|
init_capabilities();
|
|
11671
|
-
init_monetizationPolicy();
|
|
11672
12168
|
init_projectAccessStore();
|
|
11673
|
-
|
|
12169
|
+
}
|
|
12170
|
+
});
|
|
12171
|
+
|
|
12172
|
+
// ../mockup-engine/access/projectActions.ts
|
|
12173
|
+
var init_projectActions = __esm({
|
|
12174
|
+
"../mockup-engine/access/projectActions.ts"() {
|
|
12175
|
+
"use strict";
|
|
11674
12176
|
}
|
|
11675
12177
|
});
|
|
11676
12178
|
|
|
@@ -11678,6 +12180,8 @@ var init_projectLookStore = __esm({
|
|
|
11678
12180
|
var init_policy = __esm({
|
|
11679
12181
|
"../api/_lib/authorization/policy.ts"() {
|
|
11680
12182
|
"use strict";
|
|
12183
|
+
init_projectActions();
|
|
12184
|
+
init_monetizationPolicy();
|
|
11681
12185
|
}
|
|
11682
12186
|
});
|
|
11683
12187
|
|
|
@@ -11697,6 +12201,7 @@ var init_production = __esm({
|
|
|
11697
12201
|
init_entitlement();
|
|
11698
12202
|
init_monetizationPolicy();
|
|
11699
12203
|
init_projectLookStore();
|
|
12204
|
+
init_projectActions();
|
|
11700
12205
|
init_authorizeCapability();
|
|
11701
12206
|
init_capabilities();
|
|
11702
12207
|
init_policy();
|
|
@@ -12407,7 +12912,7 @@ var init_package = __esm({
|
|
|
12407
12912
|
"package.json"() {
|
|
12408
12913
|
package_default = {
|
|
12409
12914
|
name: "shotops-mcp",
|
|
12410
|
-
version: "0.9.
|
|
12915
|
+
version: "0.9.8",
|
|
12411
12916
|
private: false,
|
|
12412
12917
|
type: "module",
|
|
12413
12918
|
description: "The bundle-emitting MCP server over the @engine/@sync spine. Exposes ShotOps tools to ChatGPT, Claude Code, Cursor, and CI over Streamable HTTP with OAuth or personal API tokens. Hosted and MCP tool paths never accept, store, or forward a store-signing credential; the explicit local release CLI validates the user's existing key directly with Apple and records only its path. Renders via headless Playwright Chromium, the engine's native non-browser habitat (same path as mockup-mcp). Also ships as a free LOCAL stdio server (`npx shotops-mcp`) \u2014 same render, on your own machine, no account needed.",
|
|
@@ -12595,8 +13100,7 @@ function projectStructure(projectFile) {
|
|
|
12595
13100
|
}
|
|
12596
13101
|
function projectCopyLocales(projectFile, locales, family = BASE_SCREEN_FAMILY) {
|
|
12597
13102
|
const file = projectFile;
|
|
12598
|
-
const
|
|
12599
|
-
const captionText = normalizeCaptionText(file?.captionText, file?.captionStyles, baseLocale);
|
|
13103
|
+
const { captionText, baseLocale } = projectLocaleAxis(projectFile);
|
|
12600
13104
|
const outputCaptions = normalizeOutputCaptionText(file?.outputCaptions);
|
|
12601
13105
|
const { panelIds } = projectStructure(projectFile);
|
|
12602
13106
|
const out = [];
|
|
@@ -12613,8 +13117,7 @@ function projectCopyLocales(projectFile, locales, family = BASE_SCREEN_FAMILY) {
|
|
|
12613
13117
|
}
|
|
12614
13118
|
function projectCopyGaps(projectFile, locales, family = BASE_SCREEN_FAMILY) {
|
|
12615
13119
|
const file = projectFile;
|
|
12616
|
-
const
|
|
12617
|
-
const captionText = normalizeCaptionText(file?.captionText, file?.captionStyles, baseLocale);
|
|
13120
|
+
const { captionText, baseLocale } = projectLocaleAxis(projectFile);
|
|
12618
13121
|
const outputCaptions = normalizeOutputCaptionText(file?.outputCaptions);
|
|
12619
13122
|
const base = resolveOutputCaptionText(captionText, outputCaptions, family, baseLocale, baseLocale);
|
|
12620
13123
|
if (!Object.values(base).some((layers) => layers.some((layer) => layer.text.trim().length > 0))) return [];
|
|
@@ -13072,8 +13575,7 @@ function onePanelStyle(value, panelIndex2) {
|
|
|
13072
13575
|
}
|
|
13073
13576
|
function projectPanelCaptionLayers(projectFile, panelId, variant) {
|
|
13074
13577
|
const file = record3(projectFile);
|
|
13075
|
-
const baseLocale =
|
|
13076
|
-
const captionText = normalizeCaptionText(file?.captionText, file?.captionStyles, baseLocale);
|
|
13578
|
+
const { captionText, baseLocale } = projectLocaleAxis(projectFile);
|
|
13077
13579
|
const outputCaptions = normalizeOutputCaptionText(file?.outputCaptions);
|
|
13078
13580
|
const resolved = resolveOutputCaptionText(
|
|
13079
13581
|
captionText,
|
|
@@ -13087,6 +13589,7 @@ function projectPanelCaptionLayers(projectFile, panelId, variant) {
|
|
|
13087
13589
|
function projectPanelRecipes(projectFile, screens, targets) {
|
|
13088
13590
|
const structure = projectStructure(projectFile);
|
|
13089
13591
|
const file = record3(projectFile);
|
|
13592
|
+
const recordPreset = projectOutputAxis(projectFile).panelPresetId;
|
|
13090
13593
|
const shots = Array.isArray(file?.shots) ? file.shots : [];
|
|
13091
13594
|
const slotSizes = structure.panelIds.map(
|
|
13092
13595
|
(panelId) => shots.filter((shot) => record3(shot)?.panelId === panelId).length
|
|
@@ -13095,14 +13598,14 @@ function projectPanelRecipes(projectFile, screens, targets) {
|
|
|
13095
13598
|
const variant = projectRenderVariant(projectFile, target.locale, target.panelPresetId);
|
|
13096
13599
|
return [screens.resolve(variant).then((resolved) => {
|
|
13097
13600
|
const byShot = new Map(resolved.screenManifest.map((entry) => [entry.shotId, entry.bytes]));
|
|
13098
|
-
const dimensions = panelDimensionsFor(target.panelPresetId ??
|
|
13601
|
+
const dimensions = panelDimensionsFor(target.panelPresetId ?? recordPreset);
|
|
13099
13602
|
return structure.panelIds.flatMap((panelId, panelIndex2) => {
|
|
13100
13603
|
const screenshots2 = shots.filter((shot) => record3(shot)?.panelId === panelId).map((shot) => byShot.get(String(record3(shot)?.id))).filter((bytes) => typeof bytes === "string");
|
|
13101
13604
|
return [{
|
|
13102
13605
|
key: [target.output, target.locale, `panel-${String(panelIndex2 + 1).padStart(4, "0")}`].filter(Boolean).join("-"),
|
|
13103
13606
|
panelIndex: panelIndex2,
|
|
13104
13607
|
screenshots: screenshots2,
|
|
13105
|
-
panelPresetId: target.panelPresetId ??
|
|
13608
|
+
panelPresetId: target.panelPresetId ?? recordPreset,
|
|
13106
13609
|
look: onePanelLook(projectFile, panelIndex2, slotSizes, panelId),
|
|
13107
13610
|
captionLayers: projectPanelCaptionLayers(projectFile, panelId, variant),
|
|
13108
13611
|
backgroundViewport: { panelIndex: panelIndex2, panelCount: structure.panelIds.length },
|
|
@@ -13166,10 +13669,6 @@ function frameNamesFromProjectFile(projectFile) {
|
|
|
13166
13669
|
if (!Array.isArray(shots)) return [];
|
|
13167
13670
|
return shots.map((s) => s?.frameName).filter((n) => typeof n === "string" && n.length > 0);
|
|
13168
13671
|
}
|
|
13169
|
-
function panelPresetFromProjectFile(projectFile) {
|
|
13170
|
-
const preset = projectFile?.panelPresetId;
|
|
13171
|
-
return typeof preset === "string" && preset.length > 0 ? preset : void 0;
|
|
13172
|
-
}
|
|
13173
13672
|
function outputDevicesFromProjectFile(projectFile) {
|
|
13174
13673
|
const outputs = projectFile?.outputs;
|
|
13175
13674
|
if (!Array.isArray(outputs)) return { devices: [], dropped: [] };
|
|
@@ -13180,7 +13679,7 @@ async function loadProjectRecord(deps, projectId) {
|
|
|
13180
13679
|
const project2 = resolvedProject(resolution);
|
|
13181
13680
|
if (!project2) return { error: describeMiss(resolution, projectId) };
|
|
13182
13681
|
const rec = await deps.readProjectRecord(project2.id);
|
|
13183
|
-
if (!rec) {
|
|
13682
|
+
if (!rec || isBlankRecord(rec.record)) {
|
|
13184
13683
|
return {
|
|
13185
13684
|
error: `Project "${project2.name}" has no saved record yet \u2014 open it in ShotOps and save a strip first.`
|
|
13186
13685
|
};
|
|
@@ -13193,27 +13692,22 @@ async function loadProjectRecord(deps, projectId) {
|
|
|
13193
13692
|
};
|
|
13194
13693
|
}
|
|
13195
13694
|
function projectBaseLocale(projectFile) {
|
|
13196
|
-
|
|
13197
|
-
if (typeof file?.baseLocale === "string" && file.baseLocale) return file.baseLocale;
|
|
13198
|
-
const first = Array.isArray(file?.locales) ? file.locales[0] : void 0;
|
|
13199
|
-
return typeof first === "string" && first ? first : "en-US";
|
|
13200
|
-
}
|
|
13201
|
-
function projectLocales(projectFile) {
|
|
13202
|
-
const file = projectFile;
|
|
13203
|
-
const authored = Array.isArray(file?.locales) ? file.locales.filter((locale2) => typeof locale2 === "string" && locale2.length > 0) : [];
|
|
13204
|
-
const captionLocales = file?.captionText && typeof file.captionText === "object" ? Object.keys(file.captionText) : [];
|
|
13205
|
-
return Array.from(/* @__PURE__ */ new Set([projectBaseLocale(projectFile), ...authored, ...captionLocales]));
|
|
13695
|
+
return projectLocaleAxis(projectFile).baseLocale;
|
|
13206
13696
|
}
|
|
13207
13697
|
function projectRenderLocale(projectFile, requested) {
|
|
13698
|
+
const { locales, baseLocale } = projectLocaleAxis(projectFile);
|
|
13208
13699
|
const candidate = requested?.trim();
|
|
13209
|
-
return matchAuthoredLocale(
|
|
13700
|
+
return matchAuthoredLocale(locales, candidate) ?? candidate ?? baseLocale;
|
|
13701
|
+
}
|
|
13702
|
+
function activeRenderDevice(projectFile, renderable) {
|
|
13703
|
+
const active = projectOutputAxis(projectFile).activeDeviceId;
|
|
13704
|
+
return renderable.find((device) => device.id === active) ?? renderable[0];
|
|
13210
13705
|
}
|
|
13211
13706
|
function projectRenderVariant(projectFile, locale2, panelPresetId2) {
|
|
13212
|
-
const
|
|
13213
|
-
const preset = panelPresetId2 ?? (typeof file?.panelPresetId === "string" ? file.panelPresetId : void 0);
|
|
13214
|
-
const family = DEVICES.find((device) => device.panelPresetId === preset)?.screenFamily ?? BASE_SCREEN_FAMILY;
|
|
13707
|
+
const preset = panelPresetId2 ?? projectOutputAxis(projectFile).panelPresetId;
|
|
13215
13708
|
const baseLocale = projectBaseLocale(projectFile);
|
|
13216
|
-
|
|
13709
|
+
const resolved = projectRenderLocale(projectFile, locale2);
|
|
13710
|
+
return canonicalVariant({ family: screenFamilyForPreset(preset), locale: resolved }, baseLocale);
|
|
13217
13711
|
}
|
|
13218
13712
|
async function resolveProjectScreens(deps, loaded, supplied) {
|
|
13219
13713
|
const baseLocale = projectBaseLocale(loaded.projectFile);
|
|
@@ -13262,7 +13756,7 @@ async function resolveProjectScreens(deps, loaded, supplied) {
|
|
|
13262
13756
|
"lenient",
|
|
13263
13757
|
baseLocale
|
|
13264
13758
|
);
|
|
13265
|
-
const
|
|
13759
|
+
const storedManifest2 = parsed ? allManifestEntries(parsed) : [];
|
|
13266
13760
|
const repairFindings = [];
|
|
13267
13761
|
let canReadStored = Boolean(deps.fetchHandoffBytes || deps.readProjectScreens);
|
|
13268
13762
|
const storedSources = [];
|
|
@@ -13292,7 +13786,7 @@ async function resolveProjectScreens(deps, loaded, supplied) {
|
|
|
13292
13786
|
canReadStored = Boolean(deps.fetchHandoffBytes);
|
|
13293
13787
|
}
|
|
13294
13788
|
} else if (deps.fetchHandoffBytes) {
|
|
13295
|
-
const resolvedManifest = resolveScreenManifest(
|
|
13789
|
+
const resolvedManifest = resolveScreenManifest(storedManifest2, shots);
|
|
13296
13790
|
repairFindings.push(...resolvedManifest.findings);
|
|
13297
13791
|
for (const cell of resolvedManifest.cells) {
|
|
13298
13792
|
let pending;
|
|
@@ -13375,7 +13869,7 @@ async function resolveProjectScreens(deps, loaded, supplied) {
|
|
|
13375
13869
|
}
|
|
13376
13870
|
return { screenManifest, missing };
|
|
13377
13871
|
};
|
|
13378
|
-
return { expected, unnamed, storedCount:
|
|
13872
|
+
return { expected, unnamed, storedCount: storedManifest2.length, canReadStored, resolve: resolve14, plan, repairFindings };
|
|
13379
13873
|
}
|
|
13380
13874
|
function noProjectScreensMessage(screens, suppliedCount, toolName) {
|
|
13381
13875
|
let lead;
|
|
@@ -13393,7 +13887,7 @@ function noProjectScreensMessage(screens, suppliedCount, toolName) {
|
|
|
13393
13887
|
return `${lead} This project expects: ${screens.expected.join(", ")}. Pass each raw screenshot with its original \`name\` and optional \`variant\`, in any order \u2014 ${toolName} matches by name + coordinate.` + route;
|
|
13394
13888
|
}
|
|
13395
13889
|
function coordinateLabel(variant) {
|
|
13396
|
-
return !variant ||
|
|
13890
|
+
return !variant || isBaseVariant(variant) ? "base" : variantKey(variant);
|
|
13397
13891
|
}
|
|
13398
13892
|
function projectScreenResolutionNote(shots) {
|
|
13399
13893
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -13473,8 +13967,8 @@ async function renderProject(deps, args, scope, attempt, signal) {
|
|
|
13473
13967
|
const requested = resolveOutputDevices(args.outputs);
|
|
13474
13968
|
const chosen = requested.devices.length > 0 ? requested : outputDevicesFromProjectFile(projectFile);
|
|
13475
13969
|
const droppedNote = droppedOutputsNote(chosen.dropped, renderedSavedSet(chosen.devices));
|
|
13476
|
-
const recordPreset =
|
|
13477
|
-
const active =
|
|
13970
|
+
const recordPreset = projectOutputAxis(projectFile).panelPresetId;
|
|
13971
|
+
const active = activeRenderDevice(projectFile, chosen.devices);
|
|
13478
13972
|
const ordered = active ? [active, ...chosen.devices.filter((d) => d.id !== active.id)] : [];
|
|
13479
13973
|
const mayOverridePreset = requested.devices.length > 0 || chosen.devices.length > 1;
|
|
13480
13974
|
const requestedLocale = args.locale?.trim() || void 0;
|
|
@@ -13498,7 +13992,7 @@ async function renderProject(deps, args, scope, attempt, signal) {
|
|
|
13498
13992
|
locales: [snapshotLocale],
|
|
13499
13993
|
outputs: ordered.map((device) => device.id),
|
|
13500
13994
|
unsupportedOutputs: chosen.dropped,
|
|
13501
|
-
panelPresetId: active?.panelPresetId ?? recordPreset
|
|
13995
|
+
panelPresetId: active?.panelPresetId ?? recordPreset
|
|
13502
13996
|
},
|
|
13503
13997
|
baseLocale: projectBaseLocale(projectFile),
|
|
13504
13998
|
gaps: repairGaps(screens.repairFindings)
|
|
@@ -13519,9 +14013,9 @@ async function renderProject(deps, args, scope, attempt, signal) {
|
|
|
13519
14013
|
});
|
|
13520
14014
|
};
|
|
13521
14015
|
if (durableHostedProductionAvailable(deps) && args.preview !== true) {
|
|
13522
|
-
const activePreset = active && mayOverridePreset ? active.panelPresetId : recordPreset
|
|
14016
|
+
const activePreset = active && mayOverridePreset ? active.panelPresetId : recordPreset;
|
|
13523
14017
|
const dimensions = panelDimensionsFor(activePreset);
|
|
13524
|
-
const targets = ordered.length > 0 ? ordered.map((device) => ({ locale: snapshotLocale, panelPresetId: device.panelPresetId, output: device.id })) : [{ locale: snapshotLocale,
|
|
14018
|
+
const targets = ordered.length > 0 ? ordered.map((device) => ({ locale: snapshotLocale, panelPresetId: device.panelPresetId, output: device.id })) : [{ locale: snapshotLocale, panelPresetId: recordPreset }];
|
|
13525
14019
|
const recipes = await projectPanelRecipes(projectFile, screens, targets);
|
|
13526
14020
|
const panelCount = projectStructure(projectFile).panelIds.length || recipes.length;
|
|
13527
14021
|
const durableResolved = await screens.resolve(activeVariant);
|
|
@@ -13768,6 +14262,7 @@ var init_toolsRender = __esm({
|
|
|
13768
14262
|
init_productionRenderAccess();
|
|
13769
14263
|
init_outputMode();
|
|
13770
14264
|
init_appstore();
|
|
14265
|
+
init_recordEnvelope();
|
|
13771
14266
|
init_screenshotInput();
|
|
13772
14267
|
init_uploads();
|
|
13773
14268
|
init_resultContract();
|
|
@@ -13840,10 +14335,87 @@ function publicRefineProjectReceipt(receipt) {
|
|
|
13840
14335
|
}
|
|
13841
14336
|
};
|
|
13842
14337
|
}
|
|
13843
|
-
var init_refineProjectReceipt = __esm({
|
|
13844
|
-
"src/project/refineProjectReceipt.ts"() {
|
|
14338
|
+
var init_refineProjectReceipt = __esm({
|
|
14339
|
+
"src/project/refineProjectReceipt.ts"() {
|
|
14340
|
+
"use strict";
|
|
14341
|
+
init_monetizationPolicy();
|
|
14342
|
+
}
|
|
14343
|
+
});
|
|
14344
|
+
|
|
14345
|
+
// ../mockup-engine/recordFromStrip.ts
|
|
14346
|
+
function normalizeSourceName(raw) {
|
|
14347
|
+
const name = raw?.trim();
|
|
14348
|
+
if (!name) return null;
|
|
14349
|
+
return /\.(png|jpe?g|webp)$/i.test(name) ? name : `${name}.png`;
|
|
14350
|
+
}
|
|
14351
|
+
function frameNamesFor(shots, shotNames) {
|
|
14352
|
+
const seen = /* @__PURE__ */ new Map();
|
|
14353
|
+
return shots.map((_, i) => {
|
|
14354
|
+
const real = normalizeSourceName(shotNames?.[i]);
|
|
14355
|
+
if (!real) return `screen-${i + 1}.png`;
|
|
14356
|
+
const count = seen.get(real) ?? 0;
|
|
14357
|
+
seen.set(real, count + 1);
|
|
14358
|
+
if (count === 0) return real;
|
|
14359
|
+
const dot = real.lastIndexOf(".");
|
|
14360
|
+
return `${real.slice(0, dot)}-${count + 1}${real.slice(dot)}`;
|
|
14361
|
+
});
|
|
14362
|
+
}
|
|
14363
|
+
function recordFromStrip(input) {
|
|
14364
|
+
const { look: look2, panelIds, captionText, locale: locale2, shotNames, outputs, locales, baseLocale } = input;
|
|
14365
|
+
const panels = panelIds.map((id) => ({ id }));
|
|
14366
|
+
const frameNames = frameNamesFor(look2.shots, shotNames);
|
|
14367
|
+
const shots = look2.shots.map((s, i) => ({
|
|
14368
|
+
id: `shot-${i + 1}`,
|
|
14369
|
+
panelId: s.panelId,
|
|
14370
|
+
frameNodeId: frameNames[i],
|
|
14371
|
+
frameName: frameNames[i],
|
|
14372
|
+
bytes: null,
|
|
14373
|
+
look: s.look,
|
|
14374
|
+
thumb: null,
|
|
14375
|
+
thumbStale: true
|
|
14376
|
+
}));
|
|
14377
|
+
return serializeProject({
|
|
14378
|
+
shots,
|
|
14379
|
+
panels,
|
|
14380
|
+
selectedShotId: shots[0]?.id ?? null,
|
|
14381
|
+
panelPresetId: look2.panelPresetId,
|
|
14382
|
+
bgMode: look2.bgMode,
|
|
14383
|
+
// #556 — the CANONICAL gradient, forwarded alongside the mirror. `stripFromSlots` genuinely
|
|
14384
|
+
// resolves stops+angle (it runs every styling source through `stripGradientOf`), so omitting
|
|
14385
|
+
// them here was live data loss: a three-stop backdrop at 37° was persisted as its two-stop
|
|
14386
|
+
// mirror on the nearest axis, and the middle stops were gone from the record for good. The
|
|
14387
|
+
// sibling render path (`buildStrip.ts`, #463) always got this right.
|
|
14388
|
+
gradientStops: look2.gradientStops,
|
|
14389
|
+
gradientAngle: look2.gradientAngle,
|
|
14390
|
+
gradientFrom: look2.gradientFrom,
|
|
14391
|
+
gradientTo: look2.gradientTo,
|
|
14392
|
+
gradientDir: look2.gradientDir,
|
|
14393
|
+
panelColors: look2.panelColors,
|
|
14394
|
+
shadow: look2.shadow,
|
|
14395
|
+
// #564 — the same drop, one field away and not a mirror bug at all: `serialize.ts` turns an
|
|
14396
|
+
// absent `floorReflection` into `false`, so a render that showed a floor reflection saved a
|
|
14397
|
+
// project that did not have one (the `studio` design preset is one of the two that turn it on).
|
|
14398
|
+
floorReflection: look2.floorReflection,
|
|
14399
|
+
panelBackgrounds: look2.panelBackgrounds,
|
|
14400
|
+
captionStyles: look2.captionStyles,
|
|
14401
|
+
// #40 — the selected target devices. Passed straight to serializeProject, which runs it
|
|
14402
|
+
// through deriveOutputs: an undefined list migrates to the single device matching
|
|
14403
|
+
// `panelPresetId` (the pre-#40 record, unchanged), a supplied one is trusted verbatim minus
|
|
14404
|
+
// unknown/coming-soon ids.
|
|
14405
|
+
outputs,
|
|
14406
|
+
// #1001 — the selected languages, when the caller knows them. Omitted by every render path, and
|
|
14407
|
+
// `serializeProject` then derives the list exactly as it always has (caption keys ∪ the base).
|
|
14408
|
+
...locales ? { locales } : {},
|
|
14409
|
+
...baseLocale ? { baseLocale } : {},
|
|
14410
|
+
// Nest this render's flat caption text under its locale (default en-US) so the stored blob
|
|
14411
|
+
// is locale-correct; serializeProject's normalizeCaptionText passes a nested map through.
|
|
14412
|
+
captionText: captionText && Object.keys(captionText).length > 0 ? { [locale2 || DEFAULT_LOCALE]: captionText } : {}
|
|
14413
|
+
});
|
|
14414
|
+
}
|
|
14415
|
+
var init_recordFromStrip = __esm({
|
|
14416
|
+
"../mockup-engine/recordFromStrip.ts"() {
|
|
13845
14417
|
"use strict";
|
|
13846
|
-
|
|
14418
|
+
init_appstore();
|
|
13847
14419
|
}
|
|
13848
14420
|
});
|
|
13849
14421
|
|
|
@@ -14013,80 +14585,251 @@ var init_stripFromSlots = __esm({
|
|
|
14013
14585
|
"use strict";
|
|
14014
14586
|
init_appstore();
|
|
14015
14587
|
init_gradient();
|
|
14016
|
-
NUMERIC_STRING_FIELDS = ["roll", "
|
|
14588
|
+
NUMERIC_STRING_FIELDS = ["roll", "deviceSize", "hOffset", "vOffset", "finish", "clearcoat"];
|
|
14017
14589
|
}
|
|
14018
14590
|
});
|
|
14019
14591
|
|
|
14020
|
-
// ../mockup-engine/
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
|
|
14024
|
-
|
|
14592
|
+
// ../mockup-engine/appstore/importScreens.ts
|
|
14593
|
+
var init_importScreens = __esm({
|
|
14594
|
+
"../mockup-engine/appstore/importScreens.ts"() {
|
|
14595
|
+
"use strict";
|
|
14596
|
+
init_defaults();
|
|
14597
|
+
init_recordFromStrip();
|
|
14598
|
+
init_stripFromSlots();
|
|
14599
|
+
init_projectFileGuard();
|
|
14600
|
+
init_recordEnvelope();
|
|
14601
|
+
init_screenManifest();
|
|
14602
|
+
}
|
|
14603
|
+
});
|
|
14604
|
+
|
|
14605
|
+
// ../mockup-engine/appstore/reach.ts
|
|
14606
|
+
function reachUploadsScreens(reach) {
|
|
14607
|
+
return reach === "account";
|
|
14025
14608
|
}
|
|
14026
|
-
function
|
|
14027
|
-
|
|
14028
|
-
|
|
14029
|
-
|
|
14030
|
-
if (!real) return `screen-${i + 1}.png`;
|
|
14031
|
-
const count = seen.get(real) ?? 0;
|
|
14032
|
-
seen.set(real, count + 1);
|
|
14033
|
-
if (count === 0) return real;
|
|
14034
|
-
const dot = real.lastIndexOf(".");
|
|
14035
|
-
return `${real.slice(0, dot)}-${count + 1}${real.slice(dot)}`;
|
|
14036
|
-
});
|
|
14609
|
+
function readReach(value) {
|
|
14610
|
+
if (value === "device" || value === "account") return value;
|
|
14611
|
+
if (value === "shared") return "account";
|
|
14612
|
+
return null;
|
|
14037
14613
|
}
|
|
14038
|
-
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
|
|
14061
|
-
|
|
14062
|
-
|
|
14063
|
-
|
|
14064
|
-
|
|
14065
|
-
|
|
14066
|
-
|
|
14067
|
-
|
|
14068
|
-
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14614
|
+
var init_reach = __esm({
|
|
14615
|
+
"../mockup-engine/appstore/reach.ts"() {
|
|
14616
|
+
"use strict";
|
|
14617
|
+
}
|
|
14618
|
+
});
|
|
14619
|
+
|
|
14620
|
+
// ../mockup-engine/ops/persistenceOps.ts
|
|
14621
|
+
function envelopeOf(record8) {
|
|
14622
|
+
return record8 != null && typeof record8 === "object" && !Array.isArray(record8) ? record8 : {};
|
|
14623
|
+
}
|
|
14624
|
+
function storedManifest(record8) {
|
|
14625
|
+
const parsed = parseScreenManifest(envelopeOf(record8).handoffRefs);
|
|
14626
|
+
return parsed ? allManifestEntries(parsed) : [];
|
|
14627
|
+
}
|
|
14628
|
+
function storedCustody(record8) {
|
|
14629
|
+
const parsed = parseScreenManifest(envelopeOf(record8).screenPurgeRefs);
|
|
14630
|
+
return parsed ? allManifestEntries(parsed) : [];
|
|
14631
|
+
}
|
|
14632
|
+
function writeReach(record8, override) {
|
|
14633
|
+
if (override) return override;
|
|
14634
|
+
return readReach(envelopeOf(record8).reach) ?? "account";
|
|
14635
|
+
}
|
|
14636
|
+
function planScreenSave(args) {
|
|
14637
|
+
const kept = storedManifest(args.record);
|
|
14638
|
+
const reach = writeReach(args.record, args.reach);
|
|
14639
|
+
if (!reachUploadsScreens(reach)) {
|
|
14640
|
+
return { toUpload: [], kept, alreadyDurable: [], nothingToUpload: true };
|
|
14641
|
+
}
|
|
14642
|
+
const { toUpload } = planScreenUploads(args.cells, kept, args.shots);
|
|
14643
|
+
const going = new Set(toUpload.map((cell) => manifestShotCellKey(cell.shotId, cell.variant)));
|
|
14644
|
+
const alreadyDurable = args.cells.filter(
|
|
14645
|
+
(cell) => !going.has(manifestShotCellKey(cell.shotId, cell.variant))
|
|
14646
|
+
);
|
|
14647
|
+
return {
|
|
14648
|
+
toUpload: toUpload.map(({ shotId, variant, hash }) => ({ shotId, variant, hash })),
|
|
14649
|
+
kept,
|
|
14650
|
+
alreadyDurable: alreadyDurable.map(({ shotId, variant, hash }) => ({ shotId, variant, hash })),
|
|
14651
|
+
nothingToUpload: toUpload.length === 0
|
|
14652
|
+
};
|
|
14653
|
+
}
|
|
14654
|
+
function reserveUploads(cells, slots) {
|
|
14655
|
+
const byCell = new Map(cells.map((cell) => [manifestShotCellKey(cell.shotId, cell.variant), cell]));
|
|
14656
|
+
const reserved = [];
|
|
14657
|
+
for (const slot2 of slots) {
|
|
14658
|
+
const cell = byCell.get(manifestShotCellKey(slot2.shotId, slot2.variant));
|
|
14659
|
+
if (!cell) continue;
|
|
14660
|
+
reserved.push({
|
|
14661
|
+
cell,
|
|
14662
|
+
slot: slot2,
|
|
14663
|
+
entry: { shotId: slot2.shotId, variant: slot2.variant, hash: cell.hash, ref: slot2.ref }
|
|
14664
|
+
});
|
|
14665
|
+
}
|
|
14666
|
+
return reserved;
|
|
14667
|
+
}
|
|
14668
|
+
function recordScreenSave(args) {
|
|
14669
|
+
const reach = writeReach(args.record, args.reach);
|
|
14670
|
+
const manifest = reachUploadsScreens(reach) ? mergeManifest(args.kept, args.uploaded) : [];
|
|
14671
|
+
const custody = custodyUnion(storedCustody(args.record), args.heldCustody ?? [], args.kept, args.uploaded);
|
|
14672
|
+
const envelope = envelopeOf(args.record);
|
|
14673
|
+
return {
|
|
14674
|
+
record: rebuildEnvelope(args.record, {
|
|
14675
|
+
project: envelope.project,
|
|
14676
|
+
manifest,
|
|
14677
|
+
reach,
|
|
14678
|
+
...custody.length > 0 ? { screenPurgeRefs: custody } : {}
|
|
14679
|
+
}),
|
|
14680
|
+
manifest,
|
|
14681
|
+
custody,
|
|
14682
|
+
reach
|
|
14683
|
+
};
|
|
14684
|
+
}
|
|
14685
|
+
async function saveProjectScreens(request, transport) {
|
|
14686
|
+
const reach = writeReach(request.record, request.reach);
|
|
14687
|
+
const plan = planScreenSave({
|
|
14688
|
+
record: request.record,
|
|
14689
|
+
shots: request.shots,
|
|
14690
|
+
cells: request.cells,
|
|
14691
|
+
reach
|
|
14692
|
+
});
|
|
14693
|
+
const build = request.rebuild ?? ((rewrite) => recordScreenSave({
|
|
14694
|
+
record: request.record,
|
|
14695
|
+
kept: rewrite.manifest,
|
|
14696
|
+
uploaded: [],
|
|
14697
|
+
heldCustody: rewrite.custody,
|
|
14698
|
+
reach: rewrite.reach
|
|
14699
|
+
}).record);
|
|
14700
|
+
const overtaken = () => transport.overtaken?.() === true;
|
|
14701
|
+
const held = custodyUnion(storedCustody(request.record), request.heldCustody ?? []);
|
|
14702
|
+
const settled = (uploaded2, custody2, projectId2, stopped) => {
|
|
14703
|
+
const manifest = reachUploadsScreens(reach) ? mergeManifest(plan.kept, uploaded2) : [];
|
|
14704
|
+
return {
|
|
14705
|
+
projectId: projectId2,
|
|
14706
|
+
record: build({ manifest, custody: custody2, reach }),
|
|
14707
|
+
manifest,
|
|
14708
|
+
custody: custody2,
|
|
14709
|
+
uploaded: uploaded2,
|
|
14710
|
+
stopped
|
|
14711
|
+
};
|
|
14712
|
+
};
|
|
14713
|
+
let projectId = request.projectId;
|
|
14714
|
+
if (projectId === null) {
|
|
14715
|
+
if (!transport.insertRow) throw new Error("This project has no row yet and the transport cannot insert one");
|
|
14716
|
+
const custody2 = custodyUnion(held, plan.kept);
|
|
14717
|
+
const created = await transport.insertRow({ record: build({ manifest: plan.kept, custody: custody2, reach }) });
|
|
14718
|
+
projectId = created.projectId;
|
|
14719
|
+
if (overtaken()) return settled([], custody2, projectId, true);
|
|
14720
|
+
}
|
|
14721
|
+
if (plan.nothingToUpload) {
|
|
14722
|
+
return settled([], custodyUnion(held, plan.kept), projectId, false);
|
|
14723
|
+
}
|
|
14724
|
+
const slots = await transport.mintUploadSlots({ projectId, cells: plan.toUpload });
|
|
14725
|
+
if (overtaken()) return settled([], custodyUnion(held, plan.kept), projectId, true);
|
|
14726
|
+
const reserved = reserveUploads(request.cells, slots);
|
|
14727
|
+
const custody = custodyUnion(held, plan.kept, reserved.map(({ entry }) => entry));
|
|
14728
|
+
if (reserved.length === 0) return settled([], custody, projectId, false);
|
|
14729
|
+
await transport.saveRecord({ projectId, record: build({ manifest: plan.kept, custody, reach }) });
|
|
14730
|
+
if (overtaken()) return settled([], custody, projectId, true);
|
|
14731
|
+
const uploaded = [];
|
|
14732
|
+
for (const { entry, slot: slot2, cell } of reserved) {
|
|
14733
|
+
await transport.putBytes({ slot: slot2, bytes: cell.bytes });
|
|
14734
|
+
if (overtaken()) return settled([], custody, projectId, true);
|
|
14735
|
+
uploaded.push(entry);
|
|
14736
|
+
}
|
|
14737
|
+
const done = settled(uploaded, custody, projectId, false);
|
|
14738
|
+
await transport.saveRecord({ projectId, record: done.record });
|
|
14739
|
+
return done;
|
|
14740
|
+
}
|
|
14741
|
+
var init_persistenceOps = __esm({
|
|
14742
|
+
"../mockup-engine/ops/persistenceOps.ts"() {
|
|
14743
|
+
"use strict";
|
|
14744
|
+
init_recordEnvelope();
|
|
14745
|
+
init_importScreens();
|
|
14746
|
+
init_screenManifest();
|
|
14747
|
+
init_screenUploadPlan();
|
|
14748
|
+
init_variant();
|
|
14749
|
+
init_reach();
|
|
14750
|
+
init_reach();
|
|
14751
|
+
}
|
|
14752
|
+
});
|
|
14753
|
+
|
|
14754
|
+
// ../mockup-engine/appstore/blankProject.ts
|
|
14755
|
+
var init_blankProject = __esm({
|
|
14756
|
+
"../mockup-engine/appstore/blankProject.ts"() {
|
|
14757
|
+
"use strict";
|
|
14758
|
+
init_recordFromStrip();
|
|
14759
|
+
init_stripFromSlots();
|
|
14760
|
+
init_recordEnvelope();
|
|
14761
|
+
}
|
|
14762
|
+
});
|
|
14763
|
+
|
|
14764
|
+
// ../mockup-engine/appstore/captionEdit.ts
|
|
14765
|
+
var init_captionEdit = __esm({
|
|
14766
|
+
"../mockup-engine/appstore/captionEdit.ts"() {
|
|
14767
|
+
"use strict";
|
|
14768
|
+
init_defaults();
|
|
14769
|
+
init_captionModel();
|
|
14770
|
+
init_recordTransforms();
|
|
14771
|
+
}
|
|
14772
|
+
});
|
|
14773
|
+
|
|
14774
|
+
// ../mockup-engine/captionTranslationPlan.ts
|
|
14775
|
+
var init_captionTranslationPlan = __esm({
|
|
14776
|
+
"../mockup-engine/captionTranslationPlan.ts"() {
|
|
14777
|
+
"use strict";
|
|
14778
|
+
}
|
|
14779
|
+
});
|
|
14780
|
+
|
|
14781
|
+
// ../mockup-engine/appstore/translationTable.ts
|
|
14782
|
+
var init_translationTable = __esm({
|
|
14783
|
+
"../mockup-engine/appstore/translationTable.ts"() {
|
|
14784
|
+
"use strict";
|
|
14785
|
+
init_appStoreLocales();
|
|
14786
|
+
init_captionEdit();
|
|
14787
|
+
init_captionModel();
|
|
14788
|
+
init_localeAxis();
|
|
14789
|
+
init_captionTranslationPlan();
|
|
14790
|
+
}
|
|
14791
|
+
});
|
|
14792
|
+
|
|
14793
|
+
// ../mockup-engine/ops/editOps.ts
|
|
14794
|
+
function buildRecord(input) {
|
|
14795
|
+
const { look: look2, panelIds, captionText } = stripFromSlots({
|
|
14796
|
+
slotSizes: input.slotSizes,
|
|
14797
|
+
panelPresetId: input.panelPresetId,
|
|
14798
|
+
look: input.look ?? void 0,
|
|
14799
|
+
style: input.style ?? void 0
|
|
14800
|
+
});
|
|
14801
|
+
return recordFromStrip({
|
|
14802
|
+
look: look2,
|
|
14803
|
+
panelIds,
|
|
14804
|
+
captionText,
|
|
14805
|
+
locale: input.locale,
|
|
14806
|
+
shotNames: input.screenshotNames,
|
|
14807
|
+
outputs: input.outputs,
|
|
14808
|
+
...input.locales ? { locales: input.locales } : {},
|
|
14809
|
+
...input.baseLocale ? { baseLocale: input.baseLocale } : {}
|
|
14084
14810
|
});
|
|
14085
14811
|
}
|
|
14086
|
-
var
|
|
14087
|
-
|
|
14812
|
+
var STRIP_PREVIEW;
|
|
14813
|
+
var init_editOps = __esm({
|
|
14814
|
+
"../mockup-engine/ops/editOps.ts"() {
|
|
14088
14815
|
"use strict";
|
|
14816
|
+
init_gradient();
|
|
14817
|
+
init_variant();
|
|
14818
|
+
init_persistenceOps();
|
|
14089
14819
|
init_appstore();
|
|
14820
|
+
init_appListing();
|
|
14821
|
+
init_blankProject();
|
|
14822
|
+
init_captionEdit();
|
|
14823
|
+
init_recordEnvelope();
|
|
14824
|
+
init_translationTable();
|
|
14825
|
+
init_recordFromStrip();
|
|
14826
|
+
init_stripFromSlots();
|
|
14827
|
+
STRIP_PREVIEW = {
|
|
14828
|
+
/** The band's aspect, width ÷ height. */
|
|
14829
|
+
bandRatio: 25 / 11,
|
|
14830
|
+
/** How many frames fill the band before it starts scrolling. */
|
|
14831
|
+
framesAcross: 5
|
|
14832
|
+
};
|
|
14090
14833
|
}
|
|
14091
14834
|
});
|
|
14092
14835
|
|
|
@@ -14158,8 +14901,7 @@ var init_renderer = __esm({
|
|
|
14158
14901
|
"src/render/renderer.ts"() {
|
|
14159
14902
|
"use strict";
|
|
14160
14903
|
init_packagePaths();
|
|
14161
|
-
|
|
14162
|
-
init_recordFromStrip();
|
|
14904
|
+
init_editOps();
|
|
14163
14905
|
init_log();
|
|
14164
14906
|
init_staticServer();
|
|
14165
14907
|
init_browserInstall();
|
|
@@ -14245,23 +14987,20 @@ var init_renderer = __esm({
|
|
|
14245
14987
|
// buildRecord: the SAME styling seam the render uses (stripFromSlots → recordFromStrip), run
|
|
14246
14988
|
// in Node with NO browser. Both are pure, browser-free @engine functions and a record carries
|
|
14247
14989
|
// no bytes, so save_project persists an editable project without a multi-second Chromium render
|
|
14248
|
-
// — the fix for the Fly proxy timeout on that path.
|
|
14249
|
-
//
|
|
14250
|
-
//
|
|
14990
|
+
// — the fix for the Fly proxy timeout on that path. It is the engine's `buildRecord` op, the one
|
|
14991
|
+
// the phone's bridge calls too, so a save_project record, a render({buildRecord:true}) record and
|
|
14992
|
+
// a record built on the phone are the same record rather than three copies kept aligned by hand.
|
|
14251
14993
|
async buildRecord(input) {
|
|
14252
|
-
|
|
14994
|
+
return buildRecord({
|
|
14253
14995
|
slotSizes: input.slotSizes,
|
|
14254
14996
|
panelPresetId: input.panelPresetId,
|
|
14255
14997
|
look: input.look ?? void 0,
|
|
14256
|
-
style: input.style ?? void 0
|
|
14257
|
-
});
|
|
14258
|
-
return recordFromStrip({
|
|
14259
|
-
look: look2,
|
|
14260
|
-
panelIds,
|
|
14261
|
-
captionText,
|
|
14998
|
+
style: input.style ?? void 0,
|
|
14262
14999
|
locale: input.locale,
|
|
14263
|
-
|
|
14264
|
-
outputs: input.outputs
|
|
15000
|
+
screenshotNames: input.screenshotNames,
|
|
15001
|
+
outputs: input.outputs,
|
|
15002
|
+
locales: input.locales,
|
|
15003
|
+
baseLocale: input.baseLocale
|
|
14265
15004
|
});
|
|
14266
15005
|
}
|
|
14267
15006
|
async render(input) {
|
|
@@ -14398,6 +15137,9 @@ function parseControlPlaneResponse(op, value) {
|
|
|
14398
15137
|
case "list_projects":
|
|
14399
15138
|
if (!Array.isArray(body.projects)) throw new Error("control plane response is missing projects");
|
|
14400
15139
|
if (!object(body.ownedProjects)) throw new Error("control plane response is missing ownedProjects");
|
|
15140
|
+
for (const row2 of body.projects) {
|
|
15141
|
+
if (!object(object(row2)?.actions)) throw new Error("control plane response project row is missing actions");
|
|
15142
|
+
}
|
|
14401
15143
|
break;
|
|
14402
15144
|
case "read_look":
|
|
14403
15145
|
if (!Object.prototype.hasOwnProperty.call(body, "look")) throw new Error("control plane response is missing look");
|
|
@@ -14462,6 +15204,16 @@ function parseControlPlaneResponse(op, value) {
|
|
|
14462
15204
|
throw new Error("control plane response is missing clear_look result");
|
|
14463
15205
|
}
|
|
14464
15206
|
break;
|
|
15207
|
+
case "search_app_listing":
|
|
15208
|
+
if (typeof body.error !== "string" && !Array.isArray(body.results)) {
|
|
15209
|
+
throw new Error("control plane response is missing results");
|
|
15210
|
+
}
|
|
15211
|
+
break;
|
|
15212
|
+
case "resolve_app_listing":
|
|
15213
|
+
if (typeof body.error !== "string" && !Object.prototype.hasOwnProperty.call(body, "listing")) {
|
|
15214
|
+
throw new Error("control plane response is missing listing");
|
|
15215
|
+
}
|
|
15216
|
+
break;
|
|
14465
15217
|
case "create_project":
|
|
14466
15218
|
case "duplicate_project":
|
|
14467
15219
|
requiredString(body.id, "id");
|
|
@@ -14698,6 +15450,8 @@ function connectHostedBridge(token2) {
|
|
|
14698
15450
|
const res = await callOp(token2, "save_look", { project: projectId, look: look2, ...sourceName ? { sourceName } : {} });
|
|
14699
15451
|
return { version: res.version };
|
|
14700
15452
|
},
|
|
15453
|
+
searchAppListing: (term, country) => callOp(token2, "search_app_listing", { term, ...country ? { country } : {} }),
|
|
15454
|
+
resolveAppListing: (reference, country) => callOp(token2, "resolve_app_listing", { reference, ...country ? { country } : {} }),
|
|
14701
15455
|
async createProject(_userId, name, record8) {
|
|
14702
15456
|
const res = await callOp(token2, "create_project", { name, record: record8 });
|
|
14703
15457
|
return { id: res.id, recordVersion: res.recordVersion };
|
|
@@ -14751,7 +15505,7 @@ var init_hostedBridge = __esm({
|
|
|
14751
15505
|
}
|
|
14752
15506
|
});
|
|
14753
15507
|
|
|
14754
|
-
// ../mockup-engine/
|
|
15508
|
+
// ../mockup-engine/layoutFindingSentence.ts
|
|
14755
15509
|
function describeUnresolvedLayout(findings) {
|
|
14756
15510
|
if (!findings || findings.length === 0) return null;
|
|
14757
15511
|
const frames = Array.from(new Set(findings.flatMap((finding) => finding.frames))).sort((left, right) => left - right).map((index) => `Frame ${index + 1}`);
|
|
@@ -14762,9 +15516,49 @@ function describeUnresolvedLayout(findings) {
|
|
|
14762
15516
|
}
|
|
14763
15517
|
return `${findings.length} layout overlaps still remain${where}.`;
|
|
14764
15518
|
}
|
|
14765
|
-
var
|
|
14766
|
-
"../mockup-engine/
|
|
15519
|
+
var init_layoutFindingSentence = __esm({
|
|
15520
|
+
"../mockup-engine/layoutFindingSentence.ts"() {
|
|
15521
|
+
"use strict";
|
|
15522
|
+
}
|
|
15523
|
+
});
|
|
15524
|
+
|
|
15525
|
+
// ../mockup-engine/ops/agentTurnOps.ts
|
|
15526
|
+
function createAgentTurnOp(sources) {
|
|
15527
|
+
const assetTable = mintAgentAssets(sources);
|
|
15528
|
+
return {
|
|
15529
|
+
assetTable,
|
|
15530
|
+
run: (input, ports) => runAgentTurn({ ...input, assets: assetTable.descriptors }, {
|
|
15531
|
+
loadProject: ports.loadProject,
|
|
15532
|
+
renderComposed: ports.renderComposed,
|
|
15533
|
+
authorize: ports.authorize,
|
|
15534
|
+
emit: ports.emit,
|
|
15535
|
+
unresolvedLayout: (rendered) => describeUnresolvedLayout(ports.layoutFindings(rendered)),
|
|
15536
|
+
commit: async ({ after, assetBindings, expectedVersion }) => {
|
|
15537
|
+
const current = ports.readCustody();
|
|
15538
|
+
const custody = assetBindings.length > 0 ? bindAgentAssets(
|
|
15539
|
+
assetTable,
|
|
15540
|
+
assetBindings,
|
|
15541
|
+
current.handoffRefs,
|
|
15542
|
+
after.shots.map((shot) => ({ id: shot.id, frameName: shot.frameName }))
|
|
15543
|
+
) : null;
|
|
15544
|
+
return ports.commitProject({
|
|
15545
|
+
after,
|
|
15546
|
+
expectedVersion,
|
|
15547
|
+
custody: custody && {
|
|
15548
|
+
handoffRefs: custody.handoffRefs,
|
|
15549
|
+
screenPurgeRefs: foldDisplacedRefs(current.screenPurgeRefs, custody.displaced)
|
|
15550
|
+
}
|
|
15551
|
+
});
|
|
15552
|
+
}
|
|
15553
|
+
})
|
|
15554
|
+
};
|
|
15555
|
+
}
|
|
15556
|
+
var init_agentTurnOps = __esm({
|
|
15557
|
+
"../mockup-engine/ops/agentTurnOps.ts"() {
|
|
14767
15558
|
"use strict";
|
|
15559
|
+
init_layoutFindingSentence();
|
|
15560
|
+
init_agentAssetCustody();
|
|
15561
|
+
init_agentTurnCoordinator();
|
|
14768
15562
|
}
|
|
14769
15563
|
});
|
|
14770
15564
|
|
|
@@ -14825,10 +15619,10 @@ var init_aiShotProjection = __esm({
|
|
|
14825
15619
|
schema: z9.enum(["front", "left", "right"]).optional(),
|
|
14826
15620
|
prompt: "angle: front | left | right \u2014 a camera PRESET. Setting it discards any orbit value on that frame, so send orbit OR angle, never both."
|
|
14827
15621
|
},
|
|
14828
|
-
|
|
15622
|
+
deviceSize: {
|
|
14829
15623
|
exposure: "included",
|
|
14830
|
-
schema: z9.number().min(AI_SHOT_RANGES.
|
|
14831
|
-
prompt: `
|
|
15624
|
+
schema: z9.number().min(AI_SHOT_RANGES.deviceSize.min).max(AI_SHOT_RANGES.deviceSize.max).optional(),
|
|
15625
|
+
prompt: `deviceSize: ${AI_SHOT_RANGES.deviceSize.min}\u2013${AI_SHOT_RANGES.deviceSize.max} (percent of the default device size: 100 is the default, about 122 fills the panel height, more bleeds off an edge)`
|
|
14832
15626
|
},
|
|
14833
15627
|
hOffset: {
|
|
14834
15628
|
exposure: "included",
|
|
@@ -14899,6 +15693,16 @@ var init_aiShotProjection = __esm({
|
|
|
14899
15693
|
exposure: "included",
|
|
14900
15694
|
schema: z9.boolean().optional(),
|
|
14901
15695
|
prompt: "flatScreen, glare, lighting, reflections, clipToFrame: true | false."
|
|
15696
|
+
},
|
|
15697
|
+
deviceFrame: {
|
|
15698
|
+
exposure: "included",
|
|
15699
|
+
schema: z9.boolean().optional(),
|
|
15700
|
+
prompt: 'deviceFrame: true | false, default true. False renders a flat rounded screenshot card instead of the 3D device \u2014 no device chrome. "remove the phone frame"/"make it borderless" means deviceFrame: false. roll still rotates the flat card the same way it tilts a framed device.'
|
|
15701
|
+
},
|
|
15702
|
+
screenCornerRadius: {
|
|
15703
|
+
exposure: "included",
|
|
15704
|
+
schema: z9.number().min(AI_SHOT_RANGES.screenCornerRadius.min).max(AI_SHOT_RANGES.screenCornerRadius.max).optional(),
|
|
15705
|
+
prompt: `screenCornerRadius: ${AI_SHOT_RANGES.screenCornerRadius.min}\u2013${AI_SHOT_RANGES.screenCornerRadius.max} (percent of the flat card's width). Applies only when deviceFrame is false.`
|
|
14902
15706
|
}
|
|
14903
15707
|
};
|
|
14904
15708
|
API_AI_SHOT_INCLUDED_ENTRIES = Object.entries(API_AI_SHOT_PROJECTION).filter((entry) => entry[1].exposure === "included");
|
|
@@ -14979,7 +15783,11 @@ var init_refineProjectContracts = __esm({
|
|
|
14979
15783
|
});
|
|
14980
15784
|
framePatchSchema = z10.object({
|
|
14981
15785
|
captionStyle: captionStylePatchSchema.optional(),
|
|
14982
|
-
background: backgroundPatchSchema.optional()
|
|
15786
|
+
background: backgroundPatchSchema.optional(),
|
|
15787
|
+
// The per-frame half of the strip-wide `layout`, on the same two-scope footing as `captionStyle`
|
|
15788
|
+
// and `background` above (#266). Without it a composition asked for on SOME frames had to be
|
|
15789
|
+
// spent on ALL of them.
|
|
15790
|
+
layout: z10.enum(START_LAYOUT_IDS).optional()
|
|
14983
15791
|
});
|
|
14984
15792
|
currentCaptionStyleSchema = captionStylePatchSchema.extend({
|
|
14985
15793
|
color: currentColorSchema.optional()
|
|
@@ -15418,15 +16226,13 @@ function recordCustody(existing) {
|
|
|
15418
16226
|
};
|
|
15419
16227
|
}
|
|
15420
16228
|
function recordEnvelope(existing, state, custody) {
|
|
15421
|
-
|
|
15422
|
-
return {
|
|
15423
|
-
...stored,
|
|
16229
|
+
return rebuildEnvelope(existing, {
|
|
15424
16230
|
project: serializeProject(state),
|
|
15425
16231
|
...custody ? {
|
|
15426
|
-
|
|
16232
|
+
manifest: custody.handoffRefs,
|
|
15427
16233
|
...custody.screenPurgeRefs.length > 0 ? { screenPurgeRefs: custody.screenPurgeRefs } : {}
|
|
15428
16234
|
} : {}
|
|
15429
|
-
};
|
|
16235
|
+
});
|
|
15430
16236
|
}
|
|
15431
16237
|
function aiCaptionStyleFromLayer(layer) {
|
|
15432
16238
|
if (!layer) return {};
|
|
@@ -15748,13 +16554,14 @@ async function runMcpProjectAgentTurn(deps, input, runtime) {
|
|
|
15748
16554
|
return { ...INVALID_ATTACHMENT };
|
|
15749
16555
|
}
|
|
15750
16556
|
}
|
|
15751
|
-
const
|
|
16557
|
+
const turnOp = createAgentTurnOp(attachments.map((attachment) => ({
|
|
15752
16558
|
ref: attachment.ref,
|
|
15753
16559
|
label: attachment.label,
|
|
15754
16560
|
...attachment.width !== void 0 ? { width: attachment.width } : {},
|
|
15755
16561
|
...attachment.height !== void 0 ? { height: attachment.height } : {},
|
|
15756
16562
|
singleUse: false
|
|
15757
16563
|
})));
|
|
16564
|
+
const assetTable = turnOp.assetTable;
|
|
15758
16565
|
const turnId = randomUUID3();
|
|
15759
16566
|
const turnKey = `turn_${turnId}`;
|
|
15760
16567
|
const startedAt = input.startedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -15783,11 +16590,10 @@ async function runMcpProjectAgentTurn(deps, input, runtime) {
|
|
|
15783
16590
|
assets: assetTable.descriptors,
|
|
15784
16591
|
assetTable
|
|
15785
16592
|
};
|
|
15786
|
-
const result = await
|
|
16593
|
+
const result = await turnOp.run({
|
|
15787
16594
|
turnId,
|
|
15788
16595
|
instruction: input.instruction,
|
|
15789
16596
|
focus: input.focus,
|
|
15790
|
-
assets: assetTable.descriptors,
|
|
15791
16597
|
requestedCycle: input.requestedCycle,
|
|
15792
16598
|
startedAt,
|
|
15793
16599
|
cancelled: input.cancelled
|
|
@@ -15819,7 +16625,7 @@ async function runMcpProjectAgentTurn(deps, input, runtime) {
|
|
|
15819
16625
|
const resolved = await withBoundAttachments(stored, state, variant, boundAssets, assetTable, readAttachmentBytes);
|
|
15820
16626
|
return renderedTurn(deps, state, resolved);
|
|
15821
16627
|
},
|
|
15822
|
-
|
|
16628
|
+
layoutFindings: (rendered) => rendered.layoutFindings,
|
|
15823
16629
|
authorize: async ({ cycle, state, rendered, previous }) => {
|
|
15824
16630
|
const request = requestForPass(context, { cycle, state, rendered, previous });
|
|
15825
16631
|
const authorization = await authorizePass(
|
|
@@ -15834,21 +16640,12 @@ async function runMcpProjectAgentTurn(deps, input, runtime) {
|
|
|
15834
16640
|
const { patch: _refused, ...rest } = authorization;
|
|
15835
16641
|
return { ...rest, terminalOutcome: failureOutcome("invalid_plan", handles.failure.message) };
|
|
15836
16642
|
},
|
|
15837
|
-
|
|
15838
|
-
|
|
15839
|
-
const bound = assetBindings.length > 0 ? bindAgentAssets(
|
|
15840
|
-
assetTable,
|
|
15841
|
-
assetBindings,
|
|
15842
|
-
custody.handoffRefs,
|
|
15843
|
-
after.shots.map((shot) => ({ id: shot.id, frameName: shot.frameName }))
|
|
15844
|
-
) : null;
|
|
16643
|
+
readCustody: () => recordCustody(storedRecord),
|
|
16644
|
+
commitProject: async ({ after, expectedVersion, custody }) => {
|
|
15845
16645
|
const write = await runtime.writeProjectRecord(
|
|
15846
16646
|
project2.id,
|
|
15847
16647
|
expectedVersion,
|
|
15848
|
-
recordEnvelope(storedRecord, after,
|
|
15849
|
-
handoffRefs: bound.handoffRefs,
|
|
15850
|
-
screenPurgeRefs: foldDisplacedRefs(custody.screenPurgeRefs, bound.displaced)
|
|
15851
|
-
} : void 0),
|
|
16648
|
+
recordEnvelope(storedRecord, after, custody ?? void 0),
|
|
15852
16649
|
{ actorUserId: deps.userId }
|
|
15853
16650
|
);
|
|
15854
16651
|
if (write.ok) return { ok: true, data: { projectId: project2.id, recordVersion: write.recordVersion } };
|
|
@@ -15921,10 +16718,11 @@ var init_agentTurnAdapter = __esm({
|
|
|
15921
16718
|
"src/project/agentTurnAdapter.ts"() {
|
|
15922
16719
|
"use strict";
|
|
15923
16720
|
init_appstore();
|
|
16721
|
+
init_agentTurnOps();
|
|
16722
|
+
init_recordEnvelope();
|
|
15924
16723
|
init_agentOutcome();
|
|
15925
16724
|
init_projectRecord();
|
|
15926
16725
|
init_gradient();
|
|
15927
|
-
init_layoutFindings();
|
|
15928
16726
|
init_capabilities();
|
|
15929
16727
|
init_invalidRequest();
|
|
15930
16728
|
init_refineProjectContracts();
|
|
@@ -16263,6 +17061,11 @@ function localToolDeps(token2, options = {}) {
|
|
|
16263
17061
|
execute: (_userId, action, operationId) => bridge.productionOperation(action, operationId)
|
|
16264
17062
|
},
|
|
16265
17063
|
saveLook: (projectId, look2, sourceName) => bridge.saveLook(projectId, look2, sourceName),
|
|
17064
|
+
// #1004 — the App Store listing doors, through the bridge and nowhere else. This tier runs in
|
|
17065
|
+
// the USER's own process, so a direct Apple client here would be a second copy of the locale
|
|
17066
|
+
// and device tables on their machine; the control plane keeps exactly one.
|
|
17067
|
+
searchAppListing: (_userId, term, country) => bridgedListingCall(() => bridge.searchAppListing(term, country)),
|
|
17068
|
+
resolveAppListing: (_userId, reference, country) => bridgedListingCall(() => bridge.resolveAppListing(reference, country)),
|
|
16266
17069
|
// `ctx` is NOT forwarded as tool args — it existed so the REMOTE save_project tool could
|
|
16267
17070
|
// rebuild the record, and the control plane is a dumb writer, so the record built HERE is the
|
|
16268
17071
|
// one that gets stored. It is still READ, for one thing only: the local file paths whose
|
|
@@ -16290,83 +17093,84 @@ function localToolDeps(token2, options = {}) {
|
|
|
16290
17093
|
bridged.refineProject = (userId, input) => runLocalRefineTurn({ ...bridged, userId }, bridge, input, options.emit ? { emit: options.emit } : void 0);
|
|
16291
17094
|
return bridged;
|
|
16292
17095
|
}
|
|
17096
|
+
async function bridgedListingCall(call) {
|
|
17097
|
+
try {
|
|
17098
|
+
return await call();
|
|
17099
|
+
} catch (err) {
|
|
17100
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17101
|
+
const throttled = err instanceof ControlPlaneOperationError && err.status === 429 || /refusing new requests/i.test(message);
|
|
17102
|
+
return { error: throttled ? "throttled" : "unavailable", message };
|
|
17103
|
+
}
|
|
17104
|
+
}
|
|
16293
17105
|
async function pushScreens(bridge, projectId, record8, recordVersion, screenshots2) {
|
|
16294
17106
|
const local = localScreenPaths(screenshots2);
|
|
16295
17107
|
if (local.length === 0) return;
|
|
16296
17108
|
try {
|
|
16297
|
-
const slots = await bridge.mintScreenUploads(
|
|
16298
|
-
projectId,
|
|
16299
|
-
local.map((s) => s.name)
|
|
16300
|
-
);
|
|
16301
|
-
const uploadable = await Promise.all(slots.map(async (slot2) => {
|
|
16302
|
-
const source = local.find((s) => s.name === slot2.name);
|
|
16303
|
-
if (!source) return null;
|
|
16304
|
-
return { slot: slot2, bytes: await readFile4(source.path) };
|
|
16305
|
-
}));
|
|
16306
|
-
const prepared = uploadable.filter((item) => item !== null);
|
|
16307
|
-
if (prepared.length === 0) return;
|
|
16308
17109
|
const envelope = record8 && typeof record8 === "object" ? record8 : {};
|
|
16309
|
-
const parsedLive = parseScreenManifest(envelope.handoffRefs);
|
|
16310
|
-
const existingLive = parsedLive ? allManifestEntries(parsedLive) : [];
|
|
16311
|
-
const existingCustody = parseScreenPurgeRefs(envelope.screenPurgeRefs) ?? [];
|
|
16312
17110
|
const cloudShots = shotRefsFromProjectFile(envelope.project);
|
|
16313
|
-
const
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
screenPurgeRefs: custody
|
|
16322
|
-
}, recordVersion);
|
|
16323
|
-
const registeredVersion = registered?.recordVersion ?? recordVersion + 1;
|
|
16324
|
-
const outcomes = await Promise.allSettled(
|
|
16325
|
-
prepared.map(async ({ slot: slot2, bytes }) => {
|
|
16326
|
-
const res = await fetch(slot2.uploadUrl, {
|
|
16327
|
-
method: "PUT",
|
|
16328
|
-
headers: { "content-type": "image/png" },
|
|
16329
|
-
body: new Uint8Array(bytes)
|
|
16330
|
-
});
|
|
16331
|
-
if (!res.ok) throw new Error(`upload ${slot2.name} -> ${res.status}`);
|
|
16332
|
-
return slot2;
|
|
16333
|
-
})
|
|
16334
|
-
);
|
|
16335
|
-
const successfulRefs = {};
|
|
16336
|
-
for (const outcome of outcomes) {
|
|
16337
|
-
if (outcome.status === "fulfilled" && outcome.value.name) {
|
|
16338
|
-
successfulRefs[outcome.value.name] = outcome.value.ref;
|
|
17111
|
+
const idsByFrameName = shotIdsByFrameName(cloudShots);
|
|
17112
|
+
const cells = [];
|
|
17113
|
+
const unaddressed = [];
|
|
17114
|
+
for (const source of local) {
|
|
17115
|
+
const ids = idsByFrameName[source.name];
|
|
17116
|
+
if (ids === void 0) {
|
|
17117
|
+
unaddressed.push(source.name);
|
|
17118
|
+
continue;
|
|
16339
17119
|
}
|
|
17120
|
+
const bytes = await readFile4(source.path);
|
|
17121
|
+
const hash = await screenshotFingerprint(bytes);
|
|
17122
|
+
for (const id of ids) cells.push({ shotId: id, variant: {}, hash, bytes: new Uint8Array(bytes) });
|
|
16340
17123
|
}
|
|
16341
|
-
if (
|
|
16342
|
-
|
|
16343
|
-
throw failed2?.reason ?? new Error("no screenshot upload completed");
|
|
17124
|
+
if (unaddressed.length > 0) {
|
|
17125
|
+
logEvent("warn", "screen_upload_unaddressed", { names: unaddressed.join(", ") });
|
|
16344
17126
|
}
|
|
16345
|
-
|
|
16346
|
-
|
|
16347
|
-
|
|
17127
|
+
if (cells.length === 0) return;
|
|
17128
|
+
let version2 = recordVersion;
|
|
17129
|
+
await saveProjectScreens(
|
|
17130
|
+
{
|
|
17131
|
+
projectId,
|
|
17132
|
+
record: envelope,
|
|
17133
|
+
shots: cloudShots,
|
|
17134
|
+
cells,
|
|
17135
|
+
// #178: refs and `reach` travel together. Studio only fetches bytes from refs when the
|
|
17136
|
+
// record says the screens reach the account, so writing refs without it leaves the screens
|
|
17137
|
+
// unreachable and the "Screens missing" banner firing anyway.
|
|
17138
|
+
reach: "account",
|
|
17139
|
+
rebuild: ({ manifest, custody }) => ({
|
|
17140
|
+
...envelope,
|
|
17141
|
+
...manifest.length > 0 ? { handoffRefs: manifest, reach: "account" } : {},
|
|
17142
|
+
screenPurgeRefs: custody
|
|
17143
|
+
})
|
|
17144
|
+
},
|
|
17145
|
+
{
|
|
17146
|
+
async saveRecord({ record: next }) {
|
|
17147
|
+
const written = await bridge.updateProjectRecord(projectId, next, version2);
|
|
17148
|
+
version2 = written?.recordVersion ?? version2 + 1;
|
|
17149
|
+
},
|
|
17150
|
+
async mintUploadSlots({ cells: wanted }) {
|
|
17151
|
+
const nameById = {};
|
|
17152
|
+
for (const shot of cloudShots) nameById[shot.id] = shot.frameName;
|
|
17153
|
+
const names = wanted.map((cell) => nameById[cell.shotId]).filter((name) => !!name);
|
|
17154
|
+
const slots = await bridge.mintScreenUploads(projectId, names);
|
|
17155
|
+
return wanted.flatMap((cell) => {
|
|
17156
|
+
const slot2 = slots.find((minted) => minted.name === nameById[cell.shotId]);
|
|
17157
|
+
return slot2 ? [{ shotId: cell.shotId, variant: cell.variant, ref: slot2.ref, uploadUrl: slot2.uploadUrl }] : [];
|
|
17158
|
+
});
|
|
17159
|
+
},
|
|
17160
|
+
async putBytes({ slot: slot2, bytes }) {
|
|
17161
|
+
const res = await fetch(slot2.uploadUrl, {
|
|
17162
|
+
method: "PUT",
|
|
17163
|
+
headers: { "content-type": "image/png" },
|
|
17164
|
+
body: new Uint8Array(bytes)
|
|
17165
|
+
});
|
|
17166
|
+
if (!res.ok) throw new Error(`upload ${slot2.ref} -> ${res.status}`);
|
|
17167
|
+
}
|
|
17168
|
+
}
|
|
16348
17169
|
);
|
|
16349
|
-
const preserved = preservedEntries.filter((entry) => preservedKeep.has(entry));
|
|
16350
|
-
await bridge.updateProjectRecord(projectId, {
|
|
16351
|
-
...envelope,
|
|
16352
|
-
handoffRefs: mergeManifest(preserved, manifestFromNamedRefs(successfulRefs, cloudShots)),
|
|
16353
|
-
screenPurgeRefs: custody,
|
|
16354
|
-
reach: "account"
|
|
16355
|
-
}, registeredVersion);
|
|
16356
|
-
const failed = outcomes.find((outcome) => outcome.status === "rejected");
|
|
16357
|
-
if (failed) throw failed.reason;
|
|
16358
17170
|
} catch (err) {
|
|
16359
17171
|
logEvent("error", "screen_upload_failed", { message: err.message });
|
|
16360
17172
|
}
|
|
16361
17173
|
}
|
|
16362
|
-
function custodyUnion(...manifests) {
|
|
16363
|
-
const seen = /* @__PURE__ */ new Set();
|
|
16364
|
-
return manifests.flatMap((manifest) => manifest.filter((entry) => {
|
|
16365
|
-
if (seen.has(entry.ref)) return false;
|
|
16366
|
-
seen.add(entry.ref);
|
|
16367
|
-
return true;
|
|
16368
|
-
}));
|
|
16369
|
-
}
|
|
16370
17174
|
var SIGN_IN, SHARE_LINK_NOT_BRIDGED;
|
|
16371
17175
|
var init_localDeps = __esm({
|
|
16372
17176
|
"src/runtime/localDeps.ts"() {
|
|
@@ -16379,8 +17183,9 @@ var init_localDeps = __esm({
|
|
|
16379
17183
|
init_projectClaimClient();
|
|
16380
17184
|
init_log();
|
|
16381
17185
|
init_capabilities();
|
|
16382
|
-
init_projectRecord();
|
|
16383
17186
|
init_screenManifest();
|
|
17187
|
+
init_fingerprint();
|
|
17188
|
+
init_persistenceOps();
|
|
16384
17189
|
SIGN_IN = "not available in local (free) mode \u2014 connect the hosted ShotOps MCP and sign in for saved looks, editable projects, and share links.";
|
|
16385
17190
|
SHARE_LINK_NOT_BRIDGED = `Creating a share link is ${SIGN_IN} (bridging this needs a hosted rendering step, which local mode deliberately avoids \u2014 connect directly to the hosted MCP for share links.)`;
|
|
16386
17191
|
}
|
|
@@ -16496,7 +17301,7 @@ function canonicalJson(value) {
|
|
|
16496
17301
|
return `${JSON.stringify(stableValue(value), null, 2)}
|
|
16497
17302
|
`;
|
|
16498
17303
|
}
|
|
16499
|
-
function
|
|
17304
|
+
function sha2562(value) {
|
|
16500
17305
|
return createHash5("sha256").update(value).digest("hex");
|
|
16501
17306
|
}
|
|
16502
17307
|
async function sha256File(path) {
|
|
@@ -17238,7 +18043,7 @@ function desiredReleasePayload(manifest) {
|
|
|
17238
18043
|
return payload;
|
|
17239
18044
|
}
|
|
17240
18045
|
function computeDesiredId(payload) {
|
|
17241
|
-
return
|
|
18046
|
+
return sha2562(canonicalJson(payload));
|
|
17242
18047
|
}
|
|
17243
18048
|
function parseTextValue(value, label) {
|
|
17244
18049
|
const item = record4(value, label);
|
|
@@ -18519,8 +19324,8 @@ async function assembleDesiredRelease(input) {
|
|
|
18519
19324
|
};
|
|
18520
19325
|
const recordBytes = Buffer.from(canonicalString(project2.record), "utf8");
|
|
18521
19326
|
const projectBlob = {
|
|
18522
|
-
path: seal(
|
|
18523
|
-
sha256:
|
|
19327
|
+
path: seal(sha2562(recordBytes), { bytes: recordBytes }),
|
|
19328
|
+
sha256: sha2562(recordBytes),
|
|
18524
19329
|
bytes: recordBytes.byteLength,
|
|
18525
19330
|
mediaType: "application/json"
|
|
18526
19331
|
};
|
|
@@ -18567,7 +19372,7 @@ async function assembleDesiredRelease(input) {
|
|
|
18567
19372
|
throw new Error(`renderer returned a non-PNG panel for ${output2.id}/${locale2} at position ${index + 1}`);
|
|
18568
19373
|
}
|
|
18569
19374
|
const position = index + 1;
|
|
18570
|
-
const digest4 =
|
|
19375
|
+
const digest4 = sha2562(bytes);
|
|
18571
19376
|
artifacts.push({
|
|
18572
19377
|
role: "rendered_screenshot",
|
|
18573
19378
|
custody: "sealed",
|
|
@@ -18780,7 +19585,7 @@ function screenshotSetProjection(set) {
|
|
|
18780
19585
|
return set ? { id: set.id, deviceSet: set.deviceSet, items: mediaProjection(set.items) } : null;
|
|
18781
19586
|
}
|
|
18782
19587
|
function screenshotSetToken(set) {
|
|
18783
|
-
return
|
|
19588
|
+
return sha2562(canonicalJson(screenshotSetProjection(set)));
|
|
18784
19589
|
}
|
|
18785
19590
|
function previewTypeFor(deviceSet) {
|
|
18786
19591
|
return deviceSet.replace(/^APP_/, "");
|
|
@@ -18836,7 +19641,7 @@ function projectRemoteResource(remote, scope) {
|
|
|
18836
19641
|
}
|
|
18837
19642
|
}
|
|
18838
19643
|
function resourceToken(remote, scope) {
|
|
18839
|
-
return
|
|
19644
|
+
return sha2562(canonicalJson(projectRemoteResource(remote, scope)));
|
|
18840
19645
|
}
|
|
18841
19646
|
function scopeKey(scope) {
|
|
18842
19647
|
return scope.key === null ? scope.resource : `${scope.resource}:${scope.key}`;
|
|
@@ -18919,10 +19724,10 @@ var init_releaseDrift = __esm({
|
|
|
18919
19724
|
|
|
18920
19725
|
// src/release/releaseOperations.ts
|
|
18921
19726
|
function operationValueHash(value) {
|
|
18922
|
-
return
|
|
19727
|
+
return sha2562(canonicalJson(value));
|
|
18923
19728
|
}
|
|
18924
19729
|
function computeOperationId(input) {
|
|
18925
|
-
return
|
|
19730
|
+
return sha2562(canonicalJson({
|
|
18926
19731
|
kind: input.kind,
|
|
18927
19732
|
target: input.target,
|
|
18928
19733
|
precondition: input.precondition,
|
|
@@ -19537,7 +20342,7 @@ function assertSha(value, label) {
|
|
|
19537
20342
|
if (typeof value !== "string" || !SHA2562.test(value)) throw new Error(`${label} is not a SHA-256 digest`);
|
|
19538
20343
|
}
|
|
19539
20344
|
function valueHash(value) {
|
|
19540
|
-
return
|
|
20345
|
+
return sha2562(canonicalJson(value));
|
|
19541
20346
|
}
|
|
19542
20347
|
function assertExactReleasePlan(value) {
|
|
19543
20348
|
if (!isRecord5(value) || value.kind !== "shotops.release-plan") {
|
|
@@ -19636,7 +20441,7 @@ async function readScreenshot(root, reference) {
|
|
|
19636
20441
|
if (!bytes.subarray(0, PNG_SIGNATURE3.length).equals(PNG_SIGNATURE3)) {
|
|
19637
20442
|
throw new Error(`release screenshot is not a PNG: ${reference.source}`);
|
|
19638
20443
|
}
|
|
19639
|
-
if (
|
|
20444
|
+
if (sha2562(bytes) !== reference.sha256) throw new Error(`release screenshot content changed: ${reference.source}`);
|
|
19640
20445
|
return bytes;
|
|
19641
20446
|
}
|
|
19642
20447
|
async function atomicPrivateWrite(path, bytes) {
|
|
@@ -19748,7 +20553,7 @@ async function loadArtifact(path, now) {
|
|
|
19748
20553
|
throw new Error("release review artifact screenshot manifest does not match the plan");
|
|
19749
20554
|
}
|
|
19750
20555
|
const bytes = entries[raw.path];
|
|
19751
|
-
if (!bytes || bytes.byteLength !== raw.bytes || bytes.byteLength !== expected.get(raw.sha256).bytes || !Buffer.from(bytes.subarray(0, PNG_SIGNATURE3.length)).equals(PNG_SIGNATURE3) ||
|
|
20556
|
+
if (!bytes || bytes.byteLength !== raw.bytes || bytes.byteLength !== expected.get(raw.sha256).bytes || !Buffer.from(bytes.subarray(0, PNG_SIGNATURE3.length)).equals(PNG_SIGNATURE3) || sha2562(bytes) !== raw.sha256) {
|
|
19752
20557
|
throw new Error("release review artifact screenshot content does not match the plan");
|
|
19753
20558
|
}
|
|
19754
20559
|
screenshots2.set(raw.sha256, bytes);
|
|
@@ -19854,7 +20659,7 @@ function assertRecord(record8) {
|
|
|
19854
20659
|
assertDetail(record8.detail);
|
|
19855
20660
|
}
|
|
19856
20661
|
function chain(previous, payload) {
|
|
19857
|
-
return
|
|
20662
|
+
return sha2562(`${previous}\0${canonicalJson(payload)}`);
|
|
19858
20663
|
}
|
|
19859
20664
|
function journalFileName(attemptId) {
|
|
19860
20665
|
return `attempt-${attemptId}.ndjson`;
|
|
@@ -20129,7 +20934,7 @@ async function writePartialReceipt(path, receipt) {
|
|
|
20129
20934
|
await rename4(temporary, path);
|
|
20130
20935
|
}
|
|
20131
20936
|
function receiptDigest(payload) {
|
|
20132
|
-
return
|
|
20937
|
+
return sha2562(canonicalJson(payload));
|
|
20133
20938
|
}
|
|
20134
20939
|
var RELEASE_JOURNAL_SCHEMA, RELEASE_JOURNAL_KIND, MAX_DETAIL_TEXT, SHA2563, RELEASE_JOURNAL_STAGES, RELEASE_JOURNAL_PHASES, RELEASE_JOURNAL_STATUSES, ReleaseJournalError, SAFE_ID, JOURNAL_FILE, ATTEMPT_DIRECTORY_NAME;
|
|
20135
20940
|
var init_releaseJournal = __esm({
|
|
@@ -20983,7 +21788,7 @@ async function inspectLocalEvidence(input) {
|
|
|
20983
21788
|
let workspace = null;
|
|
20984
21789
|
try {
|
|
20985
21790
|
workspace = await verifyReleaseWorkspace(workspacePath);
|
|
20986
|
-
if (workspace.manifest.desiredId !== plan.desired.desiredId ||
|
|
21791
|
+
if (workspace.manifest.desiredId !== plan.desired.desiredId || sha2562(workspace.text) !== plan.desired.manifestSha256) {
|
|
20987
21792
|
diagnostics.push("unsupported_evidence");
|
|
20988
21793
|
}
|
|
20989
21794
|
} catch {
|
|
@@ -21283,7 +22088,7 @@ async function inspectApple(evidence, deps, diagnostics) {
|
|
|
21283
22088
|
diagnostics.push("credentials_missing");
|
|
21284
22089
|
return { editable: false, url: null, context: null };
|
|
21285
22090
|
}
|
|
21286
|
-
if (
|
|
22091
|
+
if (sha2562(setup.issuerId) !== evidence.plan.owner.issuerIdSha256 || sha2562(setup.keyId) !== evidence.plan.owner.keyIdSha256) {
|
|
21287
22092
|
diagnostics.push("credentials_missing");
|
|
21288
22093
|
return { editable: false, url: null, context: null };
|
|
21289
22094
|
}
|
|
@@ -22198,7 +23003,7 @@ async function runUpload(request, client, deps, ids, record8) {
|
|
|
22198
23003
|
} catch {
|
|
22199
23004
|
return { state: "not-applied", reason: "validation_failed" };
|
|
22200
23005
|
}
|
|
22201
|
-
if (bytes.byteLength !== request.payload.bytes ||
|
|
23006
|
+
if (bytes.byteLength !== request.payload.bytes || sha2562(bytes) !== request.payload.sha256) {
|
|
22202
23007
|
return { state: "not-applied", reason: "validation_failed" };
|
|
22203
23008
|
}
|
|
22204
23009
|
let relationshipId;
|
|
@@ -23087,7 +23892,7 @@ async function sealBackup(destination, manifest, blobs) {
|
|
|
23087
23892
|
await writeFile6(file, bytes, { mode: 384 });
|
|
23088
23893
|
const written = await stat5(file);
|
|
23089
23894
|
if (written.size !== bytes.byteLength) fail4("a recovery backup image changed size while it was being written");
|
|
23090
|
-
if (await sha256File(file) !==
|
|
23895
|
+
if (await sha256File(file) !== sha2562(bytes)) fail4("a recovery backup image changed content while it was being written");
|
|
23091
23896
|
await fsyncPath2(file, false);
|
|
23092
23897
|
}
|
|
23093
23898
|
const manifestPath = join15(temporary, BACKUP_FILENAME);
|
|
@@ -23227,7 +24032,7 @@ async function readSealedImages(workspace, manifest, selected) {
|
|
|
23227
24032
|
if (!info?.isFile() || info.isSymbolicLink()) fail4(`the sealed image for ${set.key} is missing or unsafe`);
|
|
23228
24033
|
if (info.size !== payload.bytes) fail4(`the sealed image for ${set.key} changed size since the plan was approved`);
|
|
23229
24034
|
const bytes = await readFile12(file);
|
|
23230
|
-
if (
|
|
24035
|
+
if (sha2562(bytes) !== payload.sha256) fail4(`the sealed image for ${set.key} changed since the plan was approved`);
|
|
23231
24036
|
const md5 = md5Hex(bytes);
|
|
23232
24037
|
if (md5 !== payload.md5) fail4(`the sealed image for ${set.key} does not match the checksum the plan approved`);
|
|
23233
24038
|
sealed.set(payload.sha256, { bytes, md5 });
|
|
@@ -23307,7 +24112,7 @@ async function captureBackup(client, plan, manifest, bound, destination, capture
|
|
|
23307
24112
|
fail4(`App Store Connect will not release the bytes of ${image.fileName} in ${item.selected.target.locale}/${item.selected.target.deviceSet}; nothing was deleted`);
|
|
23308
24113
|
}
|
|
23309
24114
|
const bytes = await client.download(image.asset);
|
|
23310
|
-
const digest4 =
|
|
24115
|
+
const digest4 = sha2562(bytes);
|
|
23311
24116
|
const md5 = md5Hex(bytes);
|
|
23312
24117
|
if (image.sourceFileChecksum && image.sourceFileChecksum !== md5) {
|
|
23313
24118
|
fail4(`the bytes App Store Connect returned for ${image.fileName} do not match its own checksum; nothing was deleted`);
|
|
@@ -23704,7 +24509,7 @@ function assetsFingerprint(assets) {
|
|
|
23704
24509
|
});
|
|
23705
24510
|
}
|
|
23706
24511
|
function valueHash2(value) {
|
|
23707
|
-
return
|
|
24512
|
+
return sha2562(canonicalJson(value));
|
|
23708
24513
|
}
|
|
23709
24514
|
function payloadOf(asset, role) {
|
|
23710
24515
|
return { role, source: asset.source, sha256: asset.sha256, md5: asset.md5, bytes: asset.bytes };
|
|
@@ -24317,7 +25122,7 @@ async function createReleasePlan(input, deps = {}) {
|
|
|
24317
25122
|
const approvalExpiresAt = new Date(now.getTime() + APPROVAL_TTL_MS).toISOString();
|
|
24318
25123
|
const policySha256 = valueHash2(RELEASE_POLICY);
|
|
24319
25124
|
const remoteSnapshotSha256 = valueHash2(remote);
|
|
24320
|
-
const manifestSha256 =
|
|
25125
|
+
const manifestSha256 = sha2562(loaded.text);
|
|
24321
25126
|
const commitment = mintCommitmentKey(deps.randomSource);
|
|
24322
25127
|
const payload = {
|
|
24323
25128
|
schema: PLAN_SCHEMA2,
|
|
@@ -24339,7 +25144,7 @@ async function createReleasePlan(input, deps = {}) {
|
|
|
24339
25144
|
buildSha256: loaded.manifest.build?.sha256 ?? null,
|
|
24340
25145
|
scope: loaded.manifest.scope
|
|
24341
25146
|
},
|
|
24342
|
-
owner: { issuerIdSha256:
|
|
25147
|
+
owner: { issuerIdSha256: sha2562(setup.issuerId), keyIdSha256: sha2562(setup.keyId) },
|
|
24343
25148
|
target: {
|
|
24344
25149
|
appId: remote.app.id,
|
|
24345
25150
|
appInfoId: remote.appInfo.id,
|
|
@@ -25036,7 +25841,7 @@ async function runPreflight(input, deps, recovery) {
|
|
|
25036
25841
|
if (plan.desired.desiredId !== workspace.manifest.desiredId) {
|
|
25037
25842
|
refuse2("plan_mismatch", "this plan was built from a different desired release than the workspace holds");
|
|
25038
25843
|
}
|
|
25039
|
-
if (plan.desired.manifestSha256 !==
|
|
25844
|
+
if (plan.desired.manifestSha256 !== sha2562(workspace.text)) {
|
|
25040
25845
|
refuse2("plan_mismatch", "the sealed desired release changed after this plan was built; re-plan before committing");
|
|
25041
25846
|
}
|
|
25042
25847
|
if (!EDITABLE_VERSION_STATES.has(plan.target.appStoreState)) {
|
|
@@ -25052,7 +25857,7 @@ async function runPreflight(input, deps, recovery) {
|
|
|
25052
25857
|
} catch (error) {
|
|
25053
25858
|
refuse2("setup_unavailable", error.message);
|
|
25054
25859
|
}
|
|
25055
|
-
if (
|
|
25860
|
+
if (sha2562(setup.issuerId) !== plan.owner.issuerIdSha256 || sha2562(setup.keyId) !== plan.owner.keyIdSha256) {
|
|
25056
25861
|
refuse2("owner_mismatch", "this machine's App Store Connect setup is not the one this plan was built with");
|
|
25057
25862
|
}
|
|
25058
25863
|
let appleToken;
|
|
@@ -25255,7 +26060,7 @@ async function runStages(preflight, attemptId, journal, signal, deps, now) {
|
|
|
25255
26060
|
function readSealedPayload(workspacePath) {
|
|
25256
26061
|
return async (reference) => {
|
|
25257
26062
|
const bytes = await readFile13(join17(workspacePath, ...reference.source.split("/")));
|
|
25258
|
-
if (bytes.byteLength !== reference.bytes ||
|
|
26063
|
+
if (bytes.byteLength !== reference.bytes || sha2562(bytes) !== reference.sha256) {
|
|
25259
26064
|
throw new Error("a sealed payload changed since this plan was approved");
|
|
25260
26065
|
}
|
|
25261
26066
|
return bytes;
|
|
@@ -25861,23 +26666,20 @@ function parsePrepareArgs(argv2) {
|
|
|
25861
26666
|
};
|
|
25862
26667
|
}
|
|
25863
26668
|
function authoredLocales(projectFile) {
|
|
25864
|
-
const
|
|
25865
|
-
|
|
25866
|
-
const fallback = typeof file.baseLocale === "string" && file.baseLocale.trim() ? file.baseLocale.trim() : "en-US";
|
|
25867
|
-
const result = Array.from(new Set((locales.length ? locales : [fallback]).map((locale2) => locale2.trim())));
|
|
25868
|
-
for (const locale2 of result) {
|
|
26669
|
+
const locales = projectLocaleAxis(projectFile).locales;
|
|
26670
|
+
for (const locale2 of locales) {
|
|
25869
26671
|
if (!/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/.test(locale2)) throw new Error(`project contains an unsafe locale identifier: ${locale2}`);
|
|
25870
26672
|
}
|
|
25871
|
-
return
|
|
26673
|
+
return locales;
|
|
25872
26674
|
}
|
|
25873
26675
|
function selectedOutputs(projectFile) {
|
|
25874
26676
|
const resolved = outputDevicesFromProjectFile(projectFile);
|
|
25875
26677
|
const file = projectFile;
|
|
25876
26678
|
const declared = Array.isArray(file.outputs) ? file.outputs.filter((id) => typeof id === "string" && id.length > 0) : [];
|
|
25877
26679
|
if (declared.length > 0) return { outputs: [...resolved.devices], unsupported: resolved.dropped };
|
|
25878
|
-
const preset =
|
|
26680
|
+
const preset = projectOutputAxis(projectFile).panelPresetId;
|
|
25879
26681
|
const fallback = OUTPUT_DEVICES.find((output2) => output2.panelPresetId === preset);
|
|
25880
|
-
if (!fallback) throw new Error(`project output is unavailable or unsupported (panel preset ${preset
|
|
26682
|
+
if (!fallback) throw new Error(`project output is unavailable or unsupported (panel preset ${preset})`);
|
|
25881
26683
|
return { outputs: [fallback], unsupported: [] };
|
|
25882
26684
|
}
|
|
25883
26685
|
function localReleaseDoor(token2) {
|
|
@@ -26028,6 +26830,7 @@ var RELEASE_PREPARE_USAGE, RELEASE_GC_USAGE, LOCAL_ACTOR;
|
|
|
26028
26830
|
var init_releasePrepare = __esm({
|
|
26029
26831
|
"src/release/releasePrepare.ts"() {
|
|
26030
26832
|
"use strict";
|
|
26833
|
+
init_appstore();
|
|
26031
26834
|
init_env();
|
|
26032
26835
|
init_productionRenderAccess();
|
|
26033
26836
|
init_projectResolution();
|
|
@@ -26278,7 +27081,7 @@ function pageCss() {
|
|
|
26278
27081
|
*{box-sizing:border-box}html{background:var(--paper);color:var(--ink);font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;line-height:1.5}body{margin:0}main{max-width:1240px;margin:0 auto;padding:44px 32px 96px}
|
|
26279
27082
|
.eyebrow,.section-heading>p,.integrity-lead>p{margin:0 0 8px;text-transform:uppercase;letter-spacing:.14em;font-size:.72rem;font-weight:750;color:var(--green)}h1,h2{font-family:Georgia,"Times New Roman",serif;font-weight:500;letter-spacing:-.025em}h1{font-size:clamp(2.7rem,7vw,6.4rem);line-height:.9;margin:20px 0 24px;max-width:900px}h2{font-size:2rem;line-height:1.05;margin:0}h3{font-size:1rem;margin:0}.masthead{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(260px,.55fr);gap:56px;padding:10px 0 42px;border-bottom:1px solid var(--ink)}.masthead-copy>p:last-child{max-width:700px;color:var(--muted);font-size:1.08rem}.identity{align-self:end;border-left:4px solid var(--green);padding:4px 0 4px 20px}.identity dl{margin:0}.identity div{padding:8px 0;border-bottom:1px solid var(--line)}dt{text-transform:uppercase;letter-spacing:.08em;font-size:.67rem;font-weight:750;color:var(--muted)}dd{margin:2px 0 0;font-weight:650;overflow-wrap:anywhere}.warnings{position:sticky;top:0;z-index:5;display:grid;gap:1px;margin:0;background:var(--ink);box-shadow:0 6px 18px #161b1d22}.warning{display:flex;gap:20px;align-items:baseline;padding:14px 32px;color:white}.warning strong{min-width:150px;text-transform:uppercase;letter-spacing:.1em;font-size:.7rem}.warning--partial{background:var(--red)}.warning--build{background:var(--amber)}.warning--expired{background:var(--ink)}
|
|
26280
27083
|
.integrity{display:grid;grid-template-columns:minmax(240px,.55fr) 1.45fr;margin:48px 0;background:var(--ink);color:var(--paper);border-radius:2px;overflow:hidden}.integrity-lead{padding:32px;border-right:1px solid #ffffff2a}.integrity-lead p{color:#9dd0b5}.integrity-lead span{display:block;color:#c9c4ba;margin-top:16px}.integrity dl{display:grid;grid-template-columns:1fr 1fr;margin:0}.integrity dl div{min-width:0;padding:18px 22px;border-right:1px solid #ffffff1c;border-bottom:1px solid #ffffff1c}.integrity dt{color:#aaa59c}.integrity code{font-size:.73rem;color:#fff;overflow-wrap:anywhere}.scope-card{display:grid;grid-template-columns:1.1fr 1fr 1fr 1fr;border:1px solid var(--line);background:var(--white);margin-bottom:64px}.scope-card>div{padding:24px;border-right:1px solid var(--line)}.scope-card>div:last-child{border:0}.scope-status span{display:inline-block;padding:4px 8px;background:var(--green);color:white;text-transform:uppercase;letter-spacing:.08em;font-size:.66rem;font-weight:750}.scope-card[data-scope="partial"] .scope-status span{background:var(--red)}.scope-status strong{display:block;margin-top:18px;font-family:Georgia,"Times New Roman",serif;font-size:1.5rem;line-height:1.1}.scope-card h3{text-transform:uppercase;letter-spacing:.08em;font-size:.7rem}.scope-card ul{list-style:none;margin:12px 0 0;padding:0}.scope-card li{padding:3px 0}.muted{color:var(--muted)}
|
|
26281
|
-
.operation-section,.forbidden{display:grid;grid-template-columns:230px 1fr;gap:34px;margin:64px 0}.section-heading{position:sticky;top:82px;align-self:start}.section-heading>span{display:block;margin-top:12px;color:var(--muted);font-size:.85rem}.operation-list{display:grid;gap:14px}.operation{background:var(--white);border:1px solid var(--line)}.operation>header{display:grid;grid-template-columns:42px 1fr auto;gap:14px;padding:18px 20px;border-bottom:1px solid var(--line);align-items:start}.sequence{font-family:Georgia,"Times New Roman",serif;font-size:1.3rem;color:var(--muted)}.action{padding:4px 8px;border:1px solid currentColor;text-transform:uppercase;letter-spacing:.08em;font-size:.65rem;font-weight:800;color:var(--green)}.action--clear,.action--replace{color:var(--red)}.action--noop{color:var(--muted)}.action--attach,.action--upload{color:var(--amber)}.target{display:flex;flex-wrap:wrap;gap:6px 16px;margin:7px 0 0}.target div{display:flex;gap:5px}.target dt{font-size:.6rem}.target dd{font-size:.72rem;margin:0;font-weight:600}.diff{display:grid;grid-template-columns:1fr 1fr}.value{min-width:0;padding:18px 20px}.value:first-child{border-right:1px solid var(--line)}.value>span{display:block;text-transform:uppercase;letter-spacing:.09em;font-size:.62rem;font-weight:750;color:var(--muted);margin-bottom:7px}.value pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;font:500 .82rem/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}.value--after{background:#f8f5ef}.value--empty pre{color:var(--red);font-style:italic}.verdict{font-size:1.5rem}.gallery-groups{display:grid;gap:24px}.gallery .shot-grid{grid-template-columns:repeat(auto-fill,minmax(150px,190px));gap:12px;background:none}.gallery .shot-grid figure{border:1px solid var(--line)}.gallery-group h3{text-transform:uppercase;letter-spacing:.08em;font-size:.7rem;color:var(--muted);margin:0 0 10px}.fold{border:1px solid var(--line);background:var(--white)}.fold summary{cursor:pointer;padding:16px 20px;font-weight:650;list-style:none}.fold summary::-webkit-details-marker{display:none}.fold summary::before{content:'\u25B8 ';color:var(--muted)}.fold[open] summary::before{content:'\u25BE '}.fold-list{list-style:none;margin:0;padding:0 20px 14px;display:grid;gap:6px}.operation--folded{display:flex;gap:12px;border:0;background:none;font-size:.9rem}.operation--folded .sequence{font-size:.9rem}.operation>footer{display:flex;justify-content:space-between;gap:20px;padding:10px 20px;border-top:1px solid var(--line);color:var(--muted);font-size:.68rem}.operation>footer code{overflow:hidden;text-overflow:ellipsis}.reason{font-weight:700;color:var(--amber);white-space:nowrap}.shot-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1px;background:var(--line)}.shot-grid figure{margin:0;background:var(--white);padding:14px}.shot-frame{aspect-ratio:
|
|
27084
|
+
.operation-section,.forbidden{display:grid;grid-template-columns:230px 1fr;gap:34px;margin:64px 0}.section-heading{position:sticky;top:82px;align-self:start}.section-heading>span{display:block;margin-top:12px;color:var(--muted);font-size:.85rem}.operation-list{display:grid;gap:14px}.operation{background:var(--white);border:1px solid var(--line)}.operation>header{display:grid;grid-template-columns:42px 1fr auto;gap:14px;padding:18px 20px;border-bottom:1px solid var(--line);align-items:start}.sequence{font-family:Georgia,"Times New Roman",serif;font-size:1.3rem;color:var(--muted)}.action{padding:4px 8px;border:1px solid currentColor;text-transform:uppercase;letter-spacing:.08em;font-size:.65rem;font-weight:800;color:var(--green)}.action--clear,.action--replace{color:var(--red)}.action--noop{color:var(--muted)}.action--attach,.action--upload{color:var(--amber)}.target{display:flex;flex-wrap:wrap;gap:6px 16px;margin:7px 0 0}.target div{display:flex;gap:5px}.target dt{font-size:.6rem}.target dd{font-size:.72rem;margin:0;font-weight:600}.diff{display:grid;grid-template-columns:1fr 1fr}.value{min-width:0;padding:18px 20px}.value:first-child{border-right:1px solid var(--line)}.value>span{display:block;text-transform:uppercase;letter-spacing:.09em;font-size:.62rem;font-weight:750;color:var(--muted);margin-bottom:7px}.value pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;font:500 .82rem/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}.value--after{background:#f8f5ef}.value--empty pre{color:var(--red);font-style:italic}.verdict{font-size:1.5rem}.gallery-groups{display:grid;gap:24px}.gallery .shot-grid{grid-template-columns:repeat(auto-fill,minmax(150px,190px));gap:12px;background:none}.gallery .shot-grid figure{border:1px solid var(--line)}.gallery-group h3{text-transform:uppercase;letter-spacing:.08em;font-size:.7rem;color:var(--muted);margin:0 0 10px}.fold{border:1px solid var(--line);background:var(--white)}.fold summary{cursor:pointer;padding:16px 20px;font-weight:650;list-style:none}.fold summary::-webkit-details-marker{display:none}.fold summary::before{content:'\u25B8 ';color:var(--muted)}.fold[open] summary::before{content:'\u25BE '}.fold-list{list-style:none;margin:0;padding:0 20px 14px;display:grid;gap:6px}.operation--folded{display:flex;gap:12px;border:0;background:none;font-size:.9rem}.operation--folded .sequence{font-size:.9rem}.operation>footer{display:flex;justify-content:space-between;gap:20px;padding:10px 20px;border-top:1px solid var(--line);color:var(--muted);font-size:.68rem}.operation>footer code{overflow:hidden;text-overflow:ellipsis}.reason{font-weight:700;color:var(--amber);white-space:nowrap}.shot-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1px;background:var(--line)}.shot-grid figure{margin:0;background:var(--white);padding:14px}.shot-frame{aspect-ratio:1320/2868;background:var(--wash);display:flex;align-items:center;justify-content:center;overflow:hidden}.shot-frame img{display:block;width:100%;height:100%;object-fit:contain}.shot-grid figcaption{display:grid;gap:2px;margin-top:10px}.shot-grid figcaption span,.shot-grid figcaption small{font-size:.68rem;overflow-wrap:anywhere;color:var(--muted)}.empty-section>p{margin:0;padding:28px;background:var(--wash);color:var(--muted)}
|
|
26282
27085
|
.approval .approve-body{border:1px solid var(--line);background:var(--white);padding:26px}.approval[data-approval-state="approved"] .approve-body{border-left:4px solid var(--green)}.approval[data-approval-state="expired"] .approve-body{border-left:4px solid var(--red)}.approve-body>p{margin:0 0 16px}.approve-code{display:flex;flex-wrap:wrap;align-items:baseline;gap:12px;margin:0 0 18px;padding:14px 18px;background:var(--ink);color:var(--paper)}.approve-code span{text-transform:uppercase;letter-spacing:.1em;font-size:.66rem;font-weight:750;color:#9dd0b5}.approve-code code{font:700 1.25rem/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.08em}.approve-facts{display:grid;grid-template-columns:1fr 1fr;gap:12px 24px;margin:22px 0 0}.approve-facts code{font-size:.73rem;overflow-wrap:anywhere}.approve-note{margin:20px 0 0;color:var(--muted);font-size:.82rem}
|
|
26283
27086
|
.forbidden{padding-top:54px;border-top:1px solid var(--ink)}.forbidden ul{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line)}.forbidden li{display:flex;gap:14px;padding:22px;background:var(--white)}.forbidden li>span{color:var(--red);font-size:1.4rem;line-height:1}.forbidden p{margin:5px 0 0;color:var(--muted);font-size:.82rem}.page-footer{display:flex;justify-content:space-between;gap:30px;margin-top:72px;padding-top:20px;border-top:1px solid var(--ink);color:var(--muted);font-size:.76rem}.page-footer code{overflow-wrap:anywhere}
|
|
26284
27087
|
@media(max-width:900px){main{padding:28px 20px 70px}.masthead{grid-template-columns:1fr;gap:24px}.identity{align-self:auto}.warning{padding-inline:20px}.integrity{grid-template-columns:1fr}.integrity-lead{border-right:0;border-bottom:1px solid #ffffff2a}.scope-card{grid-template-columns:1fr 1fr}.scope-card>div:nth-child(2){border-right:0}.scope-card>div:nth-child(-n+2){border-bottom:1px solid var(--line)}.operation-section,.forbidden{grid-template-columns:1fr;gap:20px}.section-heading{position:static}.forbidden ul{grid-template-columns:1fr}}
|
|
@@ -26455,7 +27258,7 @@ function serializeReleaseReviewPayload(payload) {
|
|
|
26455
27258
|
if (json.length > RELEASE_REVIEW_PAYLOAD_LIMITS.maxPayloadBytes) {
|
|
26456
27259
|
throw new Error("this plan\u2019s review content is too large to upload for remote review");
|
|
26457
27260
|
}
|
|
26458
|
-
return { bytes: new TextEncoder().encode(json), sha256:
|
|
27261
|
+
return { bytes: new TextEncoder().encode(json), sha256: sha2562(json) };
|
|
26459
27262
|
}
|
|
26460
27263
|
var init_releaseReviewPayload2 = __esm({
|
|
26461
27264
|
"src/release/releaseReviewPayload.ts"() {
|
|
@@ -27423,6 +28226,66 @@ init_toolContract();
|
|
|
27423
28226
|
init_toolResults();
|
|
27424
28227
|
init_toolsAccount();
|
|
27425
28228
|
|
|
28229
|
+
// src/tools/toolsAppListing.ts
|
|
28230
|
+
init_toolResults();
|
|
28231
|
+
|
|
28232
|
+
// src/project/appListingLink.ts
|
|
28233
|
+
init_appListing();
|
|
28234
|
+
function appListingView(listing2) {
|
|
28235
|
+
const { applied } = listing2;
|
|
28236
|
+
return {
|
|
28237
|
+
appId: listing2.trackId,
|
|
28238
|
+
bundleId: listing2.bundleId,
|
|
28239
|
+
name: listing2.trackName,
|
|
28240
|
+
seller: listing2.sellerName,
|
|
28241
|
+
...listing2.trackViewUrl ? { storeUrl: listing2.trackViewUrl } : {},
|
|
28242
|
+
...applied.iconUrl ? { iconUrl: applied.iconUrl } : {},
|
|
28243
|
+
locales: applied.locales,
|
|
28244
|
+
baseLocale: applied.baseLocale,
|
|
28245
|
+
outputs: applied.outputs,
|
|
28246
|
+
supportsIpad: applied.supportsIpad,
|
|
28247
|
+
storefront: listing2.storefront,
|
|
28248
|
+
...applied.droppedLanguageCodes.length > 0 ? { droppedLanguageCodes: applied.droppedLanguageCodes } : {}
|
|
28249
|
+
};
|
|
28250
|
+
}
|
|
28251
|
+
function linkedAppFromRecord(record8) {
|
|
28252
|
+
const listing2 = readAppListing(record8);
|
|
28253
|
+
return listing2 ? appListingView(listing2) : null;
|
|
28254
|
+
}
|
|
28255
|
+
function appListingFailureCode(kind) {
|
|
28256
|
+
const codes = {
|
|
28257
|
+
// Both are the caller's to fix with a different reference: one could not be parsed, the other
|
|
28258
|
+
// named no app. Neither is a ShotOps asset, so `asset_not_found` would point at the wrong door.
|
|
28259
|
+
invalid_reference: "invalid_input",
|
|
28260
|
+
not_found: "invalid_input",
|
|
28261
|
+
throttled: "rate_limited",
|
|
28262
|
+
unavailable: "upstream_unavailable"
|
|
28263
|
+
};
|
|
28264
|
+
return codes[kind];
|
|
28265
|
+
}
|
|
28266
|
+
|
|
28267
|
+
// src/tools/toolsAppListing.ts
|
|
28268
|
+
var APP_LISTING_SIGN_IN = "Searching the App Store needs a ShotOps account. Sign in at https://shotops.dev, or configure SHOTOPS_TOKEN on this server, and try again. You can still name the app yourself \u2014 pass `projectName` on save_project.";
|
|
28269
|
+
function failureKind(answer) {
|
|
28270
|
+
const error = answer?.error;
|
|
28271
|
+
return typeof error === "string" ? error : null;
|
|
28272
|
+
}
|
|
28273
|
+
async function handleSearchAppListing(deps, args) {
|
|
28274
|
+
if (!deps.searchAppListing) return errorResult(APP_LISTING_SIGN_IN, "authentication_required");
|
|
28275
|
+
const answer = await deps.searchAppListing(deps.userId, args.term, args.country);
|
|
28276
|
+
const kind = failureKind(answer);
|
|
28277
|
+
if (kind) {
|
|
28278
|
+
return errorResult(answer.message, appListingFailureCode(kind));
|
|
28279
|
+
}
|
|
28280
|
+
const results = answer.results.map(appListingView);
|
|
28281
|
+
const storefront = args.country?.toLowerCase() ?? "us";
|
|
28282
|
+
return textResult({
|
|
28283
|
+
ok: true,
|
|
28284
|
+
results,
|
|
28285
|
+
message: results.length === 0 ? `No App Store app matched "${args.term}" in the ${storefront} storefront. Try the exact name as it appears on the App Store, or another storefront with \`country\` \u2014 an app released in one country is invisible to a search of any other.` : `${results.length} App Store ${results.length === 1 ? "app" : "apps"} matched "${args.term}" in the ${storefront} storefront. Confirm with the user WHICH app is theirs \u2014 the icon and seller are what prove it \u2014 then pass that result\u2019s \`appId\` (or \`storeUrl\`) as \`appListing\` on save_project, and the project it creates takes the app\u2019s name, bundle ID, languages and iPhone devices. \`locales\` and \`outputs\` on each result are what the listing would apply; \`outputs\` is empty for an iPad-only app, because ShotOps renders iPhone panels only. This says nothing about who owns the app.`
|
|
28286
|
+
});
|
|
28287
|
+
}
|
|
28288
|
+
|
|
27426
28289
|
// src/tools/toolsBundle.ts
|
|
27427
28290
|
init_fastlaneBundle();
|
|
27428
28291
|
init_appstore();
|
|
@@ -27456,10 +28319,7 @@ function dedupeLocales(list) {
|
|
|
27456
28319
|
return out;
|
|
27457
28320
|
}
|
|
27458
28321
|
function projectBaseLocale2(projectFile) {
|
|
27459
|
-
|
|
27460
|
-
if (typeof file?.baseLocale === "string" && file.baseLocale) return file.baseLocale;
|
|
27461
|
-
const first = Array.isArray(file?.locales) ? file.locales[0] : void 0;
|
|
27462
|
-
return typeof first === "string" && first ? first : void 0;
|
|
28322
|
+
return projectLocaleAxis(projectFile).baseLocale;
|
|
27463
28323
|
}
|
|
27464
28324
|
var PROJECT_SOURCE_CREATE_PROJECT_ERROR = "createProject is not supported when the screenshots come from a saved `project` \u2014 that project already exists. Drop createProject to bundle it, or pass `screenshots` to render (and save) a new one.";
|
|
27465
28325
|
function joinNotes(...parts) {
|
|
@@ -28807,7 +29667,7 @@ function readPose(look2) {
|
|
|
28807
29667
|
const angle = look2.angle === "left" || look2.angle === "right" || look2.angle === "front" ? look2.angle : DEFAULT_LOOK.angle;
|
|
28808
29668
|
const cameraPos = isRecord2(look2.cameraPos) && typeof look2.cameraPos.x === "number" && Number.isFinite(look2.cameraPos.x) && typeof look2.cameraPos.y === "number" && Number.isFinite(look2.cameraPos.y) && typeof look2.cameraPos.z === "number" && Number.isFinite(look2.cameraPos.z) ? { x: look2.cameraPos.x, y: look2.cameraPos.y, z: look2.cameraPos.z } : null;
|
|
28809
29669
|
return {
|
|
28810
|
-
|
|
29670
|
+
deviceSize: normalizePose(look2.deviceSize, "deviceSize"),
|
|
28811
29671
|
hOffset: normalizePose(look2.hOffset, "hOffset"),
|
|
28812
29672
|
vOffset: normalizePose(look2.vOffset, "vOffset"),
|
|
28813
29673
|
angle,
|
|
@@ -29118,12 +29978,28 @@ init_toolContract();
|
|
|
29118
29978
|
init_capabilities();
|
|
29119
29979
|
init_authorization();
|
|
29120
29980
|
init_productionInput2();
|
|
29981
|
+
var SAVE_APP_LISTING_SIGN_IN = "Linking a project to its App Store listing needs a ShotOps account. Sign in at https://shotops.dev, or configure SHOTOPS_TOKEN on this server, and try again \u2014 or drop `appListing` and pass `projectName` to name the project yourself. Nothing was saved.";
|
|
29121
29982
|
async function handleSaveProject(deps, args) {
|
|
29122
29983
|
const looked = await resolveLook(deps, args);
|
|
29123
29984
|
if (isLookFailure(looked)) return productionInputErrorResult(looked.failure);
|
|
29124
29985
|
const { look: look2, style: style2, note: resolveNote } = looked;
|
|
29125
29986
|
const baseNote = [resolveNote, composeConflictNote(look2, style2)].filter(Boolean).join(" ") || void 0;
|
|
29126
|
-
const
|
|
29987
|
+
const creating = !args.project;
|
|
29988
|
+
let listing2 = null;
|
|
29989
|
+
if (args.appListing) {
|
|
29990
|
+
if (!deps.resolveAppListing) return errorResult(SAVE_APP_LISTING_SIGN_IN, "authentication_required");
|
|
29991
|
+
const answer = await deps.resolveAppListing(deps.userId, args.appListing);
|
|
29992
|
+
const kind = typeof answer.error === "string" ? answer.error : null;
|
|
29993
|
+
if (kind) return errorResult(answer.message, appListingFailureCode(kind));
|
|
29994
|
+
listing2 = answer.listing;
|
|
29995
|
+
if (!listing2) {
|
|
29996
|
+
return errorResult(
|
|
29997
|
+
`No App Store app matches "${args.appListing}". Search for it with search_app_listing and pass a result\u2019s \`appId\`, or drop \`appListing\` and name the project yourself. Nothing was saved.`,
|
|
29998
|
+
"invalid_input"
|
|
29999
|
+
);
|
|
30000
|
+
}
|
|
30001
|
+
}
|
|
30002
|
+
const locale2 = args.locale?.trim() || (creating && listing2 ? listing2.applied.baseLocale : "en-US");
|
|
29127
30003
|
if (!Array.isArray(args.screenshots) || args.screenshots.length === 0) {
|
|
29128
30004
|
return errorResult(
|
|
29129
30005
|
"save_project needs a non-empty `screenshots` array (its slot shape + names define the project structure).",
|
|
@@ -29142,7 +30018,8 @@ async function handleSaveProject(deps, args) {
|
|
|
29142
30018
|
return errorResult(err.message);
|
|
29143
30019
|
}
|
|
29144
30020
|
const screenshotNames = resolveScreenshotNames(entries).flat();
|
|
29145
|
-
const
|
|
30021
|
+
const listedOutputs = creating && listing2 && !args.panelPresetId && listing2.applied.outputs.length > 0 ? listing2.applied.outputs : void 0;
|
|
30022
|
+
const { devices: outputDevices } = resolveOutputDevices(args.outputs ?? listedOutputs);
|
|
29146
30023
|
let panelPresetId2 = args.panelPresetId;
|
|
29147
30024
|
if (outputDevices.length > 0) {
|
|
29148
30025
|
if (panelPresetId2 && !outputDevices.some((d) => d.panelPresetId === panelPresetId2)) {
|
|
@@ -29164,7 +30041,11 @@ async function handleSaveProject(deps, args) {
|
|
|
29164
30041
|
screenshotNames,
|
|
29165
30042
|
// Undefined when the caller passed no `outputs` — the record then migrates its single
|
|
29166
30043
|
// output from panelPresetId exactly as it did before this field existed.
|
|
29167
|
-
outputs: outputDevices.length > 0 ? outputDevices.map((d) => d.id) : void 0
|
|
30044
|
+
outputs: outputDevices.length > 0 ? outputDevices.map((d) => d.id) : void 0,
|
|
30045
|
+
// #1004 — the listing's language set, on a CREATE only. On an update these stay undefined and
|
|
30046
|
+
// `serializeProject` derives the list exactly as it always has, so re-linking an app leaves
|
|
30047
|
+
// `locales`, `baseLocale` and every per-locale caption on the record untouched.
|
|
30048
|
+
...creating && listing2 ? { locales: listing2.applied.locales, baseLocale: locale2 } : {}
|
|
29168
30049
|
});
|
|
29169
30050
|
} catch (err) {
|
|
29170
30051
|
return errorResult(`could not build the project record: ${err.message}`, "persistence_failed");
|
|
@@ -29172,7 +30053,13 @@ async function handleSaveProject(deps, args) {
|
|
|
29172
30053
|
if (record8 == null) {
|
|
29173
30054
|
return errorResult("no project record was produced \u2014 not saved.", "persistence_failed");
|
|
29174
30055
|
}
|
|
29175
|
-
const p = await persistProject(
|
|
30056
|
+
const p = await persistProject(
|
|
30057
|
+
deps,
|
|
30058
|
+
{ ...args, projectName: args.projectName?.trim() || listing2?.applied.projectName },
|
|
30059
|
+
record8,
|
|
30060
|
+
"unsigned-new",
|
|
30061
|
+
listing2
|
|
30062
|
+
);
|
|
29176
30063
|
if ("error" in p) return errorResult(p.error);
|
|
29177
30064
|
const note = ["screenNote" in p && p.screenNote ? p.screenNote : void 0, baseNote].filter(Boolean).join(" ") || void 0;
|
|
29178
30065
|
if ("status" in p) {
|
|
@@ -29186,6 +30073,7 @@ async function handleSaveProject(deps, args) {
|
|
|
29186
30073
|
panelPresetId: panelPresetId2 ?? "r69",
|
|
29187
30074
|
locale: locale2,
|
|
29188
30075
|
...outputDevices.length > 0 ? { outputs: outputDevices.map((d) => d.id) } : {},
|
|
30076
|
+
...listing2 ? { linkedApp: appListingView(listing2) } : {},
|
|
29189
30077
|
...note ? { note } : {},
|
|
29190
30078
|
// The one local path that moves bytes off this machine. Stated with the result, from the
|
|
29191
30079
|
// retention policy's own number, rather than left for the user to discover when the claim
|
|
@@ -29209,6 +30097,9 @@ async function handleSaveProject(deps, args) {
|
|
|
29209
30097
|
// #40: echoed ONLY when the caller asked for a device set — a legacy save's response body is
|
|
29210
30098
|
// byte-identical to before.
|
|
29211
30099
|
...outputDevices.length > 0 ? { outputs: outputDevices.map((d) => d.id) } : {},
|
|
30100
|
+
// #1004 — the app this save linked, echoed so the caller sees the identity the project now
|
|
30101
|
+
// carries. Absent on every save that named no `appListing`.
|
|
30102
|
+
...listing2 ? { linkedApp: appListingView(listing2) } : {},
|
|
29212
30103
|
...note ? { note } : {},
|
|
29213
30104
|
// #662 — this is the ONE tool in the contract whose job is a project write, and the envelope
|
|
29214
30105
|
// says so: `projectMutation: true` here, false on every render. That pair is what makes
|
|
@@ -29230,17 +30121,12 @@ function reachFromRecord(record8) {
|
|
|
29230
30121
|
function storedScreenshotReport(deps, record8, projectFile) {
|
|
29231
30122
|
const frames = frameNamesFromProjectFile(projectFile);
|
|
29232
30123
|
const file = projectFile;
|
|
29233
|
-
const
|
|
29234
|
-
const locales = Array.from(
|
|
29235
|
-
/* @__PURE__ */ new Set([
|
|
29236
|
-
baseLocale,
|
|
29237
|
-
...Array.isArray(file.locales) ? file.locales.filter((locale2) => typeof locale2 === "string") : []
|
|
29238
|
-
])
|
|
29239
|
-
);
|
|
30124
|
+
const { locales, baseLocale } = projectLocaleAxis(projectFile);
|
|
29240
30125
|
const outputIds = Array.isArray(file.outputs) ? file.outputs.filter((id) => typeof id === "string") : [];
|
|
29241
|
-
const presetFamily = typeof file.panelPresetId === "string" ? DEVICES.find((device) => device.panelPresetId === file.panelPresetId)?.screenFamily : void 0;
|
|
29242
30126
|
const families = Array.from(
|
|
29243
|
-
new Set(
|
|
30127
|
+
new Set(
|
|
30128
|
+
outputIds.length > 0 ? outputIds.map(screenFamilyForDevice) : [screenFamilyForPreset(projectOutputAxis(projectFile).panelPresetId)]
|
|
30129
|
+
)
|
|
29244
30130
|
);
|
|
29245
30131
|
const parsed = parseScreenManifest(record8?.handoffRefs, "lenient", baseLocale);
|
|
29246
30132
|
const manifest = parsed ? allManifestEntries(parsed) : [];
|
|
@@ -29316,6 +30202,7 @@ async function handleReadProject(deps, args) {
|
|
|
29316
30202
|
const projectFile = projectFileFromRecord(rec.record);
|
|
29317
30203
|
const layout = deriveLayoutReport(projectFile);
|
|
29318
30204
|
const screenshots2 = storedScreenshotReport(deps, rec.record, projectFile);
|
|
30205
|
+
const linkedApp = linkedAppFromRecord(rec.record);
|
|
29319
30206
|
const historyDecision = await deps.authorizeCapability({
|
|
29320
30207
|
subject: { kind: "account", userId: deps.userId, mode: "api_token" },
|
|
29321
30208
|
capability: CAPABILITIES.LOOK_HISTORY_READ,
|
|
@@ -29338,9 +30225,13 @@ async function handleReadProject(deps, args) {
|
|
|
29338
30225
|
...historyDenial ? {} : { heldVersion: project2.heldVersion, versions },
|
|
29339
30226
|
history: historyDenial ?? { available: true, capability: CAPABILITIES.LOOK_HISTORY_READ },
|
|
29340
30227
|
screenshots: screenshots2,
|
|
30228
|
+
// #1004 — the App Store app this project is linked to, read off the stored envelope. Omitted
|
|
30229
|
+
// entirely for a project that carries none, so a project created without one reads exactly as
|
|
30230
|
+
// it did before.
|
|
30231
|
+
...linkedApp ? { linkedApp } : {},
|
|
29341
30232
|
project_file: projectFile,
|
|
29342
30233
|
...layout ? { layout } : {},
|
|
29343
|
-
message: `Full project for "${project2.name}". project_file is the designer's current strip (frame order in \`panels\`, per-locale words in \`captionText\`, the language list in \`locales\`, the caption base the untranslated locales inherit in \`baseLocale\`, styling). To detect changes, remember updatedAt and compare on your next read_project \u2014 a newer value means the designer edited it. \`layout\` names the composition template the strip is on, derived from its values \u2014 nothing stores it, so it is never stale. ${storedScreenshotSentence(screenshots2, project2.id)
|
|
30234
|
+
message: `Full project for "${project2.name}". project_file is the designer's current strip (frame order in \`panels\`, per-locale words in \`captionText\`, the language list in \`locales\`, the caption base the untranslated locales inherit in \`baseLocale\`, styling). To detect changes, remember updatedAt and compare on your next read_project \u2014 a newer value means the designer edited it. \`layout\` names the composition template the strip is on, derived from its values \u2014 nothing stores it, so it is never stale. ` + (linkedApp ? `This project is linked to the App Store app "${linkedApp.name}" by ${linkedApp.seller} (${linkedApp.bundleId}) \u2014 \`linkedApp\` carries its identity, artwork and the languages and devices the listing mapped. Advisory: it says nothing about who owns the app. ` : "") + storedScreenshotSentence(screenshots2, project2.id)
|
|
29344
30235
|
});
|
|
29345
30236
|
}
|
|
29346
30237
|
|
|
@@ -29573,6 +30464,12 @@ var DESTRUCTIVE_WRITE = {
|
|
|
29573
30464
|
idempotentHint: false,
|
|
29574
30465
|
openWorldHint: false
|
|
29575
30466
|
};
|
|
30467
|
+
var OPEN_WORLD_READ = {
|
|
30468
|
+
readOnlyHint: true,
|
|
30469
|
+
destructiveHint: false,
|
|
30470
|
+
idempotentHint: true,
|
|
30471
|
+
openWorldHint: true
|
|
30472
|
+
};
|
|
29576
30473
|
var OPEN_WORLD_DESTRUCTIVE_WRITE = {
|
|
29577
30474
|
readOnlyHint: false,
|
|
29578
30475
|
destructiveHint: true,
|
|
@@ -29607,7 +30504,7 @@ function registerTools(server, deps) {
|
|
|
29607
30504
|
"render_strip",
|
|
29608
30505
|
{
|
|
29609
30506
|
title: "Render App Store screenshots",
|
|
29610
|
-
description: 'Make the store screenshots: raw app screen captures in, finished marketing panels out. Each screenshot is composited into a styled 3D device mockup, laid out as an App Store screenshot strip with the headlines you pass, and returned as per-panel PNGs at the App Store panel size `panelPresetId` names. This is the tool for a strip built from screenshots you have in hand; to reproduce one a designer already saved, use render_project. In ChatGPT, call import_screenshot first and pass the returned refs in the preferred flat form: { "screenshots": [{ "ref": "\u2026" }] } \u2014 each flat entry is one panel, and nested slots only when ONE panel should hold several phones. Art-direct it with `style`/`look` or a project\'s saved look, iterate with a low-res `preview` at a quarter the render cost, and take results `inline` or as `urls` for large payloads. Screenshots can vary per store locale via { "locales": \u2026 } entries \u2014 `locale` picks which variant renders (missing variants fall back to en-US). For real, full-resolution screenshots call request_screenshot_upload first \u2014 inline base64 is for small payloads only. CALL describe_look FIRST when nobody has said how the strip should look \u2014 it returns the five palette presets, the seven layout templates and every styling field with its default, and guessing a colour scheme out of 30 raw fields is how strips end up looking improvised. PICK `style.layout` ONCE, BEFORE ANYTHING ELSE \u2014 off the EDGE the user asked for if they named one, otherwise off the LONGEST HEADLINE in the set: "standard" reserves 2 lines (~36 characters) with the whole device under them, "bleed" reserves 4 (~72) with the device running off the bottom edge, "top-bleed" inverts that. The side bleeds carry the device into the NEIGHBOURING screenshot so the set reads as one swipe \u2014 "lean-right" / "lean-left" put the headline in a narrow column beside it (~26 characters), "swipe" pushes a third of the device across and keeps the full-width headline, "corner-bleed" goes off the bottom and the right at once. A side bleed is for the WHOLE set: apply one to every panel or none, and never mirror it on neighbouring panels \u2014 two devices leaning toward each other overlap. The text region is RESERVED, NOT FITTED \u2014 held at full size whether or not the headline fills it, because a fixed reservation is the only thing that makes every device in a swiped SET land on the same line. Do not close a gap by shrinking vOffset on the short panels, and never assemble a composition out of raw
|
|
30507
|
+
description: 'Make the store screenshots: raw app screen captures in, finished marketing panels out. Each screenshot is composited into a styled 3D device mockup, laid out as an App Store screenshot strip with the headlines you pass, and returned as per-panel PNGs at the App Store panel size `panelPresetId` names. This is the tool for a strip built from screenshots you have in hand; to reproduce one a designer already saved, use render_project. In ChatGPT, call import_screenshot first and pass the returned refs in the preferred flat form: { "screenshots": [{ "ref": "\u2026" }] } \u2014 each flat entry is one panel, and nested slots only when ONE panel should hold several phones. Art-direct it with `style`/`look` or a project\'s saved look, iterate with a low-res `preview` at a quarter the render cost, and take results `inline` or as `urls` for large payloads. Screenshots can vary per store locale via { "locales": \u2026 } entries \u2014 `locale` picks which variant renders (missing variants fall back to en-US). For real, full-resolution screenshots call request_screenshot_upload first \u2014 inline base64 is for small payloads only. CALL describe_look FIRST when nobody has said how the strip should look \u2014 it returns the five palette presets, the seven layout templates and every styling field with its default, and guessing a colour scheme out of 30 raw fields is how strips end up looking improvised. PICK `style.layout` ONCE, BEFORE ANYTHING ELSE \u2014 off the EDGE the user asked for if they named one, otherwise off the LONGEST HEADLINE in the set: "standard" reserves 2 lines (~36 characters) with the whole device under them, "bleed" reserves 4 (~72) with the device running off the bottom edge, "top-bleed" inverts that. The side bleeds carry the device into the NEIGHBOURING screenshot so the set reads as one swipe \u2014 "lean-right" / "lean-left" put the headline in a narrow column beside it (~26 characters), "swipe" pushes a third of the device across and keeps the full-width headline, "corner-bleed" goes off the bottom and the right at once. A side bleed is for the WHOLE set: apply one to every panel or none, and never mirror it on neighbouring panels \u2014 two devices leaning toward each other overlap. The text region is RESERVED, NOT FITTED \u2014 held at full size whether or not the headline fills it, because a fixed reservation is the only thing that makes every device in a swiped SET land on the same line. Do not close a gap by shrinking vOffset on the short panels, and never assemble a composition out of raw deviceSize/vOffset numbers: pick a layout, then override one field if you must (the template expands first, your field wins). Past the reservation the caption band grows down INTO the phone \u2014 the failure to avoid. THE RESPONSE ANSWERS BACK: an optional `layout` block names every caption sitting on a device, every caption collision and every device pair overlapping without reading as composed, by frame number and depth. When the repair is deterministic it also carries `layout.correctedLook` \u2014 pass that straight back as `look` to re-render clean. No `layout` key means the composition measured clean. Read it before you show the panels. This tool ONLY renders images: it never creates or updates a project, and the result says so \u2014 `contract.effects.projectMutation` is false on every render. A render with no `project` also carries a `nextStep` with action `offer_save_project`, which states that no project exists and names save_project as the door. That is an OFFER to put to the user, never consent already given: save only after they show they want this work preserved, shared, reused or kept editable, and if you cannot ask them, return the panels with the offer standing. No store credential is involved.',
|
|
29611
30508
|
inputSchema: renderStripAdvertisedShape,
|
|
29612
30509
|
outputSchema: renderStripToolOutputSchema,
|
|
29613
30510
|
// ADDITIVE, not destructive: this can mint private generated assets but cannot change a
|
|
@@ -29681,6 +30578,18 @@ function registerTools(server, deps) {
|
|
|
29681
30578
|
},
|
|
29682
30579
|
seam("read_project", readProjectShape, (args) => handleReadProject(deps, args))
|
|
29683
30580
|
);
|
|
30581
|
+
server.registerTool(
|
|
30582
|
+
"search_app_listing",
|
|
30583
|
+
{
|
|
30584
|
+
title: "Find an app on the App Store",
|
|
30585
|
+
description: "Find the user\u2019s app on the App Store so the project you save carries its real identity instead of a name you typed: search by app or developer name and every result comes back with the app\u2019s exact name, its seller, its icon, its bundle ID and its numeric app ID, plus the App Store Connect languages and the iPhone device set ShotOps would apply. Let the USER confirm which one is theirs \u2014 the icon and the seller name are what prove it \u2014 then pass that result\u2019s `appId` (or `storeUrl`) as `appListing` on save_project. Pass `country` to search another storefront; an app released in one country is invisible to a search of any other. Free and unmetered: it renders nothing, saves nothing and changes no project. Finding a public listing proves nothing about who owns the app, and no Apple or store credential is involved. Needs an account.",
|
|
30586
|
+
inputSchema: searchAppListingShape,
|
|
30587
|
+
outputSchema: searchAppListingToolOutputSchema,
|
|
30588
|
+
annotations: OPEN_WORLD_READ,
|
|
30589
|
+
_meta: toolMeta(deps, "search_app_listing", "Searching the App Store\u2026", "App Store searched")
|
|
30590
|
+
},
|
|
30591
|
+
seam("search_app_listing", searchAppListingShape, (args) => handleSearchAppListing(deps, args))
|
|
30592
|
+
);
|
|
29684
30593
|
server.registerTool(
|
|
29685
30594
|
"render_project",
|
|
29686
30595
|
{
|