create-opentray 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +21 -13
  2. package/dist/{bin-BQb2Bjcu.mjs → bin-Bz16rwKs.mjs} +249 -313
  3. package/dist/bin-Bz16rwKs.mjs.map +1 -0
  4. package/dist/bin-CrMTyTux.d.mts +751 -0
  5. package/dist/bin-CrMTyTux.d.mts.map +1 -0
  6. package/dist/bin.d.mts +2 -15
  7. package/dist/bin.mjs +2 -2
  8. package/dist/{decode-ico-BHd2POHf.mjs → decode-ico-DDEIV7iZ.mjs} +2 -2
  9. package/dist/{decode-ico-BHd2POHf.mjs.map → decode-ico-DDEIV7iZ.mjs.map} +1 -1
  10. package/dist/encode-CTEluLqt.mjs +167 -0
  11. package/dist/encode-CTEluLqt.mjs.map +1 -0
  12. package/dist/icon-codec-Bs3XehqZ.mjs +20 -0
  13. package/dist/icon-codec-Bs3XehqZ.mjs.map +1 -0
  14. package/dist/index.d.mts +1 -724
  15. package/dist/index.d.mts.map +1 -1
  16. package/dist/index.mjs +1 -1
  17. package/dist/rolldown-runtime-DJK8HYOj.mjs +34 -0
  18. package/dist/shell/assets/ghostty-web.js +1 -1
  19. package/dist/shell/assets/index.css +1 -1
  20. package/dist/shell/assets/index.js +2 -9
  21. package/dist/shell/assets/index2.js +9 -2
  22. package/dist/shell/assets/input.js +1 -1
  23. package/dist/shell/assets/main.js +8 -8
  24. package/dist/shell/assets/terminal-pane.js +2 -2
  25. package/dist/shell/assets/terminal.js +1 -1
  26. package/dist/shell/assets/toolbar.js +1 -0
  27. package/dist/shell/index.html +1 -1
  28. package/dist/shell/terminal.html +1 -1
  29. package/dist/shell/{browse.html → toolbar.html} +3 -3
  30. package/dist/skill/SKILL.md +14 -11
  31. package/dist/skill/references/cli-reference.md +1 -1
  32. package/dist/skill/references/how-it-works.md +15 -9
  33. package/dist/webui/assets/ghostty-web.js +1 -1
  34. package/dist/webui/assets/index.css +1 -1
  35. package/dist/webui/assets/index.js +2 -9
  36. package/dist/webui/assets/index2.js +9 -2
  37. package/dist/webui/assets/input.js +1 -1
  38. package/dist/webui/assets/main.js +8 -8
  39. package/dist/webui/assets/terminal-pane.js +2 -2
  40. package/dist/webui/assets/terminal.js +1 -1
  41. package/dist/webui/assets/toolbar.js +1 -0
  42. package/dist/webui/index.html +1 -1
  43. package/dist/webui/terminal.html +1 -1
  44. package/dist/webui/{browse.html → toolbar.html} +3 -3
  45. package/package.json +6 -6
  46. package/skill/SKILL.md +14 -11
  47. package/skill/references/cli-reference.md +1 -1
  48. package/skill/references/how-it-works.md +15 -9
  49. package/dist/bin-BQb2Bjcu.mjs.map +0 -1
  50. package/dist/bin.d.mts.map +0 -1
  51. package/dist/shell/assets/browse.js +0 -1
  52. package/dist/webui/assets/browse.js +0 -1
@@ -1,4 +1,6 @@
1
+ import { r as __toESM$1, t as __commonJSMin$1 } from "./rolldown-runtime-DJK8HYOj.mjs";
1
2
  import { _ as assetsDirectory, a as decodeImage, c as encodeImagePng, d as rasterizeSvg, f as resizeImage, g as glyphLadderFingerprint, h as embeddedGlyphFont, i as cropImage, l as insideImage, m as GLYPH_FONT_FAMILY, n as cloneImage, o as decodeImageFile, p as trimBounds, r as containImage, s as emptyImageOf, t as brightPixelRatio, u as pasteImage } from "./raster-DeMbA2zI.mjs";
3
+ import { i as encodeIco, n as encodeDensePng, r as encodeIcns, t as LINUX_SIZES } from "./encode-CTEluLqt.mjs";
2
4
  import { createRequire } from "node:module";
3
5
  import crypto, { createHash, randomBytes } from "node:crypto";
4
6
  import fs, { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
@@ -9,42 +11,9 @@ import { execFile, spawn, spawnSync } from "node:child_process";
9
11
  import net from "node:net";
10
12
  import { fileURLToPath } from "node:url";
11
13
  import { getSvgPath } from "figma-squircle";
12
- import { IconIcns, IconIco } from "@shockpkg/icon-encoder";
13
14
  import { resolveDefaultDarwinAppBundlePath, sanitizeAppBundleName } from "@opentray/packaging";
14
15
  import { promisify } from "node:util";
15
16
  import { createServer } from "node:http";
16
- //#region \0rolldown/runtime.js
17
- var __create$1 = Object.create;
18
- var __defProp$1 = Object.defineProperty;
19
- var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
20
- var __getOwnPropNames$1 = Object.getOwnPropertyNames;
21
- var __getProtoOf$1 = Object.getPrototypeOf;
22
- var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
23
- var __commonJSMin$1 = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
24
- var __exportAll$1 = (all, no_symbols) => {
25
- let target = {};
26
- for (var name in all) __defProp$1(target, name, {
27
- get: all[name],
28
- enumerable: true
29
- });
30
- if (!no_symbols) __defProp$1(target, Symbol.toStringTag, { value: "Module" });
31
- return target;
32
- };
33
- var __copyProps$1 = (to, from, except, desc) => {
34
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames$1(from), i = 0, n = keys.length, key; i < n; i++) {
35
- key = keys[i];
36
- if (!__hasOwnProp$1.call(to, key) && key !== except) __defProp$1(to, key, {
37
- get: ((k) => from[k]).bind(null, key),
38
- enumerable: !(desc = __getOwnPropDesc$1(from, key)) || desc.enumerable
39
- });
40
- }
41
- return to;
42
- };
43
- var __toESM$1 = (mod, isNodeMode, target) => (target = mod != null ? __create$1(__getProtoOf$1(mod)) : {}, __copyProps$1(isNodeMode || !mod || !mod.__esModule ? __defProp$1(target, "default", {
44
- value: mod,
45
- enumerable: true
46
- }) : target, mod));
47
- //#endregion
48
17
  //#region packages/core/src/app-id.ts
49
18
  /** A token that looks like a command option, e.g. `--xx`, `-p`, or `--port=8080`. */
50
19
  const isOptionToken$1 = (token) => token.startsWith("-") && token.length > 1;
@@ -6110,167 +6079,6 @@ parse$2.Node = Node;
6110
6079
  parse$2.TextNode = TextNode;
6111
6080
  parse$2.NodeType = NodeType;
6112
6081
  //#endregion
6113
- //#region ../icon/src/encode.ts
6114
- /**
6115
- * Platform encoders. PNG payloads carry explicit 72 DPI density: the WASM PNG
6116
- * encoder emits no `pHYs` chunk (spike-verified), so density is injected with
6117
- * a minimal chunk surgery (insert/replace after IHDR, CRC32 over type+data).
6118
- * ICNS keeps the ten-tag explicit representation set; ICO and Linux PNGs keep
6119
- * their ladders. All encoders consume PNG bytes so the density law applies to
6120
- * every emitted pixel payload.
6121
- */
6122
- var encode_exports = /* @__PURE__ */ __exportAll$1({
6123
- ICNS_REPRESENTATIONS: () => ICNS_REPRESENTATIONS,
6124
- ICO_SIZES: () => ICO_SIZES,
6125
- LINUX_SIZES: () => LINUX_SIZES,
6126
- encodeDensePng: () => encodeDensePng,
6127
- encodeIcns: () => encodeIcns,
6128
- encodeIco: () => encodeIco,
6129
- pngWithDensity: () => pngWithDensity
6130
- });
6131
- /** Pixels per metre for 72 DPI (72 × 39.3701 ≈ 2834.65, the Apple value). */
6132
- const PIXELS_PER_METRE_72DPI = 2835;
6133
- const ICNS_REPRESENTATIONS = [
6134
- {
6135
- tag: "ic12",
6136
- size: 64
6137
- },
6138
- {
6139
- tag: "ic07",
6140
- size: 128
6141
- },
6142
- {
6143
- tag: "ic13",
6144
- size: 256
6145
- },
6146
- {
6147
- tag: "ic08",
6148
- size: 256
6149
- },
6150
- {
6151
- tag: "ic04",
6152
- size: 16
6153
- },
6154
- {
6155
- tag: "ic14",
6156
- size: 512
6157
- },
6158
- {
6159
- tag: "ic09",
6160
- size: 512
6161
- },
6162
- {
6163
- tag: "ic05",
6164
- size: 32
6165
- },
6166
- {
6167
- tag: "ic10",
6168
- size: 1024
6169
- },
6170
- {
6171
- tag: "ic11",
6172
- size: 32
6173
- }
6174
- ];
6175
- const ICO_SIZES = [
6176
- 16,
6177
- 24,
6178
- 32,
6179
- 48,
6180
- 64,
6181
- 128,
6182
- 256
6183
- ];
6184
- const LINUX_SIZES = [
6185
- 16,
6186
- 32,
6187
- 48,
6188
- 64,
6189
- 128,
6190
- 256,
6191
- 512
6192
- ];
6193
- const CRC_TABLE = (() => {
6194
- const table = new Array(256);
6195
- for (let n = 0; n < 256; n += 1) {
6196
- let c = n;
6197
- for (let k = 0; k < 8; k += 1) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
6198
- table[n] = c >>> 0;
6199
- }
6200
- return table;
6201
- })();
6202
- const crc32 = (bytes) => {
6203
- let crc = 4294967295;
6204
- for (let i = 0; i < bytes.length; i += 1) crc = CRC_TABLE[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
6205
- return (crc ^ 4294967295) >>> 0;
6206
- };
6207
- const chunkLength = (view, offset) => view.getUint32(offset);
6208
- const chunkType = (bytes, offset) => String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
6209
- const PHYS_DATA = (() => {
6210
- const data = /* @__PURE__ */ new Uint8Array(9);
6211
- const view = new DataView(data.buffer);
6212
- view.setUint32(0, PIXELS_PER_METRE_72DPI);
6213
- view.setUint32(4, PIXELS_PER_METRE_72DPI);
6214
- data[8] = 1;
6215
- return data;
6216
- })();
6217
- /**
6218
- * Ensure a PNG carries exactly one `pHYs` chunk at 72 DPI, inserted after
6219
- * IHDR. Every pre-existing `pHYs` chunk (however many a malformed input
6220
- * carried) is dropped so the emitted density cannot drift.
6221
- */
6222
- const pngWithDensity = (png) => {
6223
- const view = new DataView(png.buffer, png.byteOffset, png.byteLength);
6224
- let offset = 8;
6225
- let ihdrEnd = -1;
6226
- const segments = [];
6227
- const tail = [];
6228
- while (offset + 12 <= png.length) {
6229
- const length = chunkLength(view, offset);
6230
- const type = chunkType(png, offset + 4);
6231
- const chunkEnd = offset + 12 + length;
6232
- if (type === "IHDR") {
6233
- ihdrEnd = chunkEnd;
6234
- segments.push(png.subarray(offset, chunkEnd));
6235
- } else if (type !== "pHYs") (ihdrEnd === -1 ? segments : tail).push(png.subarray(offset, chunkEnd));
6236
- offset = chunkEnd;
6237
- if (type === "IEND") break;
6238
- }
6239
- if (ihdrEnd === -1) throw new Error("pngWithDensity: IHDR chunk not found");
6240
- const chunk = new Uint8Array(12 + PHYS_DATA.length);
6241
- const chunkView = new DataView(chunk.buffer);
6242
- chunkView.setUint32(0, PHYS_DATA.length);
6243
- chunk.set(new TextEncoder().encode("pHYs"), 4);
6244
- chunk.set(PHYS_DATA, 8);
6245
- chunkView.setUint32(8 + PHYS_DATA.length, crc32(chunk.subarray(4, 8 + PHYS_DATA.length)));
6246
- return concat(png.subarray(0, 8), ...segments, chunk, ...tail);
6247
- };
6248
- const concat = (...parts) => {
6249
- const total = parts.reduce((sum, part) => sum + part.byteLength, 0);
6250
- const out = new Uint8Array(total);
6251
- let offset = 0;
6252
- for (const part of parts) {
6253
- out.set(part, offset);
6254
- offset += part.byteLength;
6255
- }
6256
- return out;
6257
- };
6258
- /** Encode RGBA pixels to a density-carrying PNG. */
6259
- const encodeDensePng = async (image) => pngWithDensity(await encodeImagePng(image));
6260
- /** Encode the ten-tag ICNS from per-size PNG payloads (macOS variant source). */
6261
- const encodeIcns = async (pngAt) => {
6262
- const icns = new IconIcns();
6263
- icns.toc = true;
6264
- for (const { tag, size } of ICNS_REPRESENTATIONS) await icns.addFromPng(await pngAt(size), [tag], false);
6265
- return icns.encode();
6266
- };
6267
- /** Encode the ICO ladder from per-size PNG payloads (Windows variant source). */
6268
- const encodeIco = async (pngAt) => {
6269
- const ico = new IconIco();
6270
- for (const size of ICO_SIZES) await ico.addFromPng(await pngAt(size), null, false);
6271
- return ico.encode();
6272
- };
6273
- //#endregion
6274
6082
  //#region ../icon/src/generate.ts
6275
6083
  const ICON_SIZE = 1024;
6276
6084
  const TILE_INSET = 64;
@@ -6709,7 +6517,7 @@ const composeAppIcon = async (options) => {
6709
6517
  pasteImage(macOSVariant, macOSContent, macOSMargin, macOSMargin);
6710
6518
  const writeVariant = async (image, suffix) => {
6711
6519
  const path = join(compositionDir, `app-composited${suffix}.png`);
6712
- const { encodeDensePng } = await Promise.resolve().then(() => encode_exports);
6520
+ const { encodeDensePng } = await import("./encode-CTEluLqt.mjs").then((n) => n.a);
6713
6521
  await writeFile(path, await encodeDensePng(image));
6714
6522
  return path;
6715
6523
  };
@@ -7043,19 +6851,13 @@ const defaultFetch = {
7043
6851
  if (response === void 0) return {
7044
6852
  ok: false,
7045
6853
  status: 0,
7046
- body: "",
7047
- headers: {}
6854
+ body: ""
7048
6855
  };
7049
6856
  const body = await response.text();
7050
- const headers = {};
7051
- response.headers.forEach((value, key) => {
7052
- headers[key.toLowerCase()] = value;
7053
- });
7054
6857
  return {
7055
6858
  ok: response.ok,
7056
6859
  status: response.status,
7057
- body,
7058
- headers
6860
+ body
7059
6861
  };
7060
6862
  },
7061
6863
  async bytes(url, timeoutMs = 5e3) {
@@ -7078,22 +6880,6 @@ const defaultFetch = {
7078
6880
  /** Cap on downloaded candidates per scrape. */
7079
6881
  const MAX_ICON_DOWNLOADS = 8;
7080
6882
  /**
7081
- * Whether the page's response headers permit embedding by a third-party
7082
- * origin (the toolbar wrapper): rejects on X-Frame-Options (except
7083
- * ALLOWALL) and on CSP frame-ancestors without a wildcard. Absent both
7084
- * policies, embedding is allowed by default — same default browsers use.
7085
- */
7086
- const responseHeadersAllowEmbedding = (headers) => {
7087
- const xfo = (headers["x-frame-options"] ?? "").trim().toUpperCase();
7088
- if (xfo.length > 0 && !xfo.split(",").map((token) => token.trim()).includes("ALLOWALL")) return false;
7089
- const csp = (headers["content-security-policy"] ?? "").toLowerCase();
7090
- for (const match of csp.matchAll(/frame-ancestors\s+([^;]+)/gu)) {
7091
- const sources = (match[1] ?? "").trim();
7092
- if (sources.length === 0 || !sources.split(/\s+/u).includes("*")) return false;
7093
- }
7094
- return true;
7095
- };
7096
- /**
7097
6883
  * Scrape title and ALL icon candidates from an arbitrary http(s) URL.
7098
6884
  * Never throws: failures return `ok: false` with whatever partial identity
7099
6885
  * was found. (`scrapeService` is the loopback-port wrapper over this.)
@@ -7151,8 +6937,7 @@ const scrapeUrl = async (url, options = {}) => {
7151
6937
  title,
7152
6938
  iconPath: originals[0]?.path,
7153
6939
  ...originals[0] === void 0 ? {} : { iconUrl: originals[0].url },
7154
- icons,
7155
- frameEmbeddable: responseHeadersAllowEmbedding(page.headers)
6940
+ icons
7156
6941
  };
7157
6942
  };
7158
6943
  /**
@@ -7169,8 +6954,7 @@ const deriveUrlPresets = async (url, options = {}) => {
7169
6954
  const appName = title !== void 0 && title.length > 0 ? title.length > PRESET_TITLE_MAX ? `${title.slice(0, PRESET_TITLE_MAX - 1).trimEnd()}…` : title : void 0;
7170
6955
  return {
7171
6956
  ...appName === void 0 ? {} : { appName },
7172
- ...scraped.iconPath === void 0 ? {} : { appIconPath: scraped.iconPath },
7173
- ...scraped.frameEmbeddable === void 0 ? {} : { frameEmbeddable: scraped.frameEmbeddable }
6957
+ ...scraped.iconPath === void 0 ? {} : { appIconPath: scraped.iconPath }
7174
6958
  };
7175
6959
  };
7176
6960
  /**
@@ -7280,8 +7064,8 @@ const isIcoContainer = (bytes) => bytes.length >= 8 && bytes[0] === 0 && bytes[1
7280
7064
  * from the decoder's RGBA through the shared kernel encoder.
7281
7065
  */
7282
7066
  const extractLargestIcoFrame = async (ico) => {
7283
- const decodeIco = (await import("./decode-ico-BHd2POHf.mjs").then((m) => /* @__PURE__ */ __toESM$1(m.default, 1))).default;
7284
- const { toPngBuffer } = await Promise.resolve().then(() => icon_codec_exports);
7067
+ const decodeIco = (await import("./decode-ico-DDEIV7iZ.mjs").then((m) => /* @__PURE__ */ __toESM$1(m.default, 1))).default;
7068
+ const { toPngBuffer } = await import("./icon-codec-Bs3XehqZ.mjs").then((n) => n.t);
7285
7069
  let best;
7286
7070
  for (const frame of decodeIco(ico)) {
7287
7071
  const width = frame.width;
@@ -7391,21 +7175,6 @@ const writeIconTemp = async (bytes, dir) => {
7391
7175
  return path;
7392
7176
  };
7393
7177
  //#endregion
7394
- //#region packages/core/src/icon-codec.ts
7395
- var icon_codec_exports = /* @__PURE__ */ __exportAll$1({ toPngBuffer: () => toPngBuffer });
7396
- const toPngBuffer = async (rgba, width, height, channels) => {
7397
- const pixels = width * height;
7398
- const image = emptyImageOf(width, height);
7399
- if (channels === 3) for (let i = 0; i < pixels; i += 1) {
7400
- image.data[i * 4] = rgba[i * 3] ?? 0;
7401
- image.data[i * 4 + 1] = rgba[i * 3 + 1] ?? 0;
7402
- image.data[i * 4 + 2] = rgba[i * 3 + 2] ?? 0;
7403
- image.data[i * 4 + 3] = 255;
7404
- }
7405
- else image.data.set(rgba.subarray(0, pixels * 4));
7406
- return Buffer.from(await encodeImagePng(image));
7407
- };
7408
- //#endregion
7409
7178
  //#region packages/core/src/command-run.ts
7410
7179
  const SHELL_METACHARS = /[<>&|;$`"'%]/u;
7411
7180
  /** True when the command line needs a shell (only Windows uses cmd /c). */
@@ -7811,6 +7580,113 @@ const runExecFileCapture = (command, args) => new Promise((resolve, reject) => {
7811
7580
  });
7812
7581
  });
7813
7582
  //#endregion
7583
+ //#region packages/core/src/toolbar-carrier.ts
7584
+ /**
7585
+ * The generated-entry helper source. The embedding template supplies
7586
+ * `column`/`fixed`/`grow` (imported from "@opentray/ext-webview" beside
7587
+ * `WebviewExt`) — the carrier deliberately performs no imports of its own so
7588
+ * both templates stay single-import consumers of the facade.
7589
+ */
7590
+ const toolbarCarrierSource = () => `// Toolbar navigation carrier (add-webview-orchestration plan D12/D13):
7591
+ // composes the native navigation toolbar over one content webview and drives
7592
+ // it through the create-private message channel. Channel failure is an
7593
+ // exposed bug (D12: no fallback path) — errors land in app.log through
7594
+ // options.log instead of being silently swallowed.
7595
+ const attachToolbarCarrier = async (shell, options) => {
7596
+ // Per-child bridge policy (D2): the toolbar page is trusted shell UI and
7597
+ // gets exactly the channel surface; the content webview gets NO policy, so
7598
+ // it is bridgeless regardless of what it loads.
7599
+ await shell.createWebview({
7600
+ id: "toolbar",
7601
+ url: options.toolbarUrl,
7602
+ bridge: { webviewId: true, messageChannels: true },
7603
+ });
7604
+ const content = await shell.createWebview({ id: "content", url: options.contentUrl });
7605
+ // Declarative layered layout (D8 sugar): toolbar pinned at a fixed 44px
7606
+ // height, content filling the remainder; resize is recomputed natively.
7607
+ await shell.setLayout(column([fixed("toolbar", 44), grow("content")]));
7608
+ const channel = await shell.createMessageChannel({ target: "toolbar" });
7609
+ const note = (error) => {
7610
+ const message = error instanceof Error ? error.message : String(error);
7611
+ void options.log("toolbar carrier: " + message);
7612
+ };
7613
+ // Address-bar truth (D13): the content webview's urlChange events ARE the
7614
+ // address bar's source of truth — pushed over the channel, never scraped
7615
+ // from a wrapper document.
7616
+ const pushUrl = async (url) => {
7617
+ await channel.post({ kind: "url", url: String(url) });
7618
+ };
7619
+ content.onUrlChange((event) => {
7620
+ void pushUrl(event.url).catch(note);
7621
+ });
7622
+ // Loading affordance input (D24): the content webview's loadState pushes
7623
+ // (phase always; progress/errorCode best-effort) reach the toolbar page
7624
+ // over the same channel. The toolbar page owns the progress-bar state
7625
+ // machine — this carrier forwards verbatim and adds no interpretation, so
7626
+ // a missed "failed" frame (macOS dead-port loads finish about:blank
7627
+ // honestly) is converged by whatever terminal frame does arrive.
7628
+ const pushLoadState = async (event) => {
7629
+ const frame = { kind: "load-state", phase: event.phase, url: String(event.url) };
7630
+ if (typeof event.progress === "number") frame.progress = event.progress;
7631
+ if (typeof event.errorCode === "number") frame.errorCode = event.errorCode;
7632
+ await channel.post(frame);
7633
+ };
7634
+ content.onLoadState((event) => {
7635
+ void pushLoadState(event).catch(note);
7636
+ });
7637
+ if (options.titleFollows === true) {
7638
+ // Sync defaults project onto the CONTENT document (the real target page).
7639
+ // A windowOnly session has no primary webview for native titleSync, and
7640
+ // the v1 frozen facade exposes no host set-title command, so the entry is
7641
+ // the one-way projector through the native re-show title update
7642
+ // (apply_reused_show_updates): show({title, windowOnly}) on an existing
7643
+ // session updates the window title without touching content or
7644
+ // visibility. Icon following stays opt-in and carries no window-level
7645
+ // option here because a bridgeless content child is not a favicon
7646
+ // emitter (documented v1 limitation).
7647
+ const projectTitle = (title) => {
7648
+ if (typeof title === "string" && title.length > 0) {
7649
+ void shell.show({ title, windowOnly: true }).catch(note);
7650
+ }
7651
+ };
7652
+ content.onTitleChange((event) => {
7653
+ projectTitle(event.title);
7654
+ });
7655
+ }
7656
+ // Command surface (D12): instructions and state queries arrive as channel
7657
+ // payloads; back/forward drive the content webview's NATIVE history and
7658
+ // reload re-navigates to the current URL through the native navigate
7659
+ // command (never evaluate(location.reload()) on a wrapper).
7660
+ channel.onMessage((payload) => {
7661
+ const message = typeof payload === "object" && payload !== null ? payload : {};
7662
+ const kind = message.kind;
7663
+ if (kind === "navigate" && typeof message.url === "string" && message.url.length > 0) {
7664
+ void content.navigate(message.url).catch(note);
7665
+ return;
7666
+ }
7667
+ if (kind === "back") {
7668
+ void content.back().catch(note);
7669
+ return;
7670
+ }
7671
+ if (kind === "forward") {
7672
+ void content.forward().catch(note);
7673
+ return;
7674
+ }
7675
+ if (kind === "reload") {
7676
+ void content.getUrl().then((state) => content.navigate(state.url)).catch(note);
7677
+ return;
7678
+ }
7679
+ if (kind === "get-url") {
7680
+ void content.getUrl().then((state) => pushUrl(state.url)).catch(note);
7681
+ }
7682
+ });
7683
+ // Seed the address bar with the current truth (pre-subscription buffering
7684
+ // on both endpoints covers the race before the toolbar page registers).
7685
+ void content.getUrl().then((state) => pushUrl(state.url)).catch(note);
7686
+ return { content, channel };
7687
+ };
7688
+ `;
7689
+ //#endregion
7814
7690
  //#region packages/core/src/entry-template.ts
7815
7691
  /** The generated app entry: supervises the command, owns tray + window. */
7816
7692
  const createEntrySource = (config) => {
@@ -7826,7 +7702,7 @@ import { dirname, resolve } from "node:path";
7826
7702
  import { fileURLToPath } from "node:url";
7827
7703
 
7828
7704
  import { createTray } from "opentray";
7829
- import { WebviewExt } from "@opentray/ext-webview";
7705
+ import { WebviewExt, column, fixed, grow } from "@opentray/ext-webview";
7830
7706
 
7831
7707
  const PROJECT_DIR = dirname(fileURLToPath(import.meta.url));
7832
7708
 
@@ -7847,11 +7723,20 @@ const config = ${JSON.stringify(config, null, 2)};
7847
7723
  const appLogPath = resolve(PROJECT_DIR, "app.log");
7848
7724
  await mkdir(dirname(appLogPath), { recursive: true });
7849
7725
  const logSink = appendFile.bind(undefined, appLogPath);
7726
+ const logNote = (message) => { void logSink("[create-opentray] " + message + "\\n", "utf8"); };
7850
7727
  const sleep = (ms) => new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
7851
7728
 
7852
7729
  const shellOptions = ${JSON.stringify(config.shell ?? null)};
7853
7730
  const showTerminal = shellOptions !== null && shellOptions.showTerminal === true;
7854
- const showAddressBar = shellOptions !== null && shellOptions.showAddressBar === true;
7731
+ // Toolbar carrier (add-webview-orchestration D13): window.toolbar is the one
7732
+ // canonical toolbar fact for BOTH application forms; the retired legacy shell
7733
+ // address-bar input is deliberately not read (stale frozen occurrences are
7734
+ // ignored by loose parsing).
7735
+ const toolbarMode = config.window.toolbar === true;
7736
+ const titleFollows = config.window.titleFollowsDocument !== false;
7737
+ const iconFollows = config.window.iconFollowsDocument === true;
7738
+
7739
+ ${toolbarCarrierSource()}
7855
7740
 
7856
7741
  // Configured env overlay (advanced command options); empty by default.
7857
7742
  const commandEnv = ${JSON.stringify(config.command.env ?? {})};
@@ -7923,7 +7808,8 @@ const main = async () => {
7923
7808
  }
7924
7809
 
7925
7810
  // Shell host (D2): unconditional — the terminal window is every app's
7926
- // abnormal-exit surface, and the address bar consumes it too.
7811
+ // abnormal-exit surface, and the toolbar carrier's page asset is served
7812
+ // from the same host (its navigation surface stays channel-only, D12).
7927
7813
  const shellApi = await import("./app-shell-server.mjs");
7928
7814
  const shellPort = await shellApi.listenShell();
7929
7815
 
@@ -8023,11 +7909,11 @@ const main = async () => {
8023
7909
  const devtools = config.developerMode === true ? { devtools: true } : {};
8024
7910
 
8025
7911
  // Dedicated windows: one terminal window (always available; initially
8026
- // visible only under showTerminal), one window per listened port — an
8027
- // address-bar wrapper page when enabled, the direct service URL otherwise.
8028
- // Each window gets its OWN extension mount (tray.extend per window): one
8029
- // mount owns exactly one native webview slot, so dedicated windows never
8030
- // collide on content.
7912
+ // visible only under showTerminal), one window per listened port — the
7913
+ // native toolbar carrier when window.toolbar is on, the direct service URL
7914
+ // otherwise. Each window gets its OWN extension mount (tray.extend per
7915
+ // window = its own window session), so dedicated windows never collide on
7916
+ // content.
8031
7917
  const serviceWindows = new Map();
8032
7918
  let terminalWindow = null;
8033
7919
 
@@ -8060,28 +7946,45 @@ const main = async () => {
8060
7946
  const ensureServiceWindow = async (port) => {
8061
7947
  if (serviceWindows.has(port)) return;
8062
7948
  const direct = \`http://127.0.0.1:\${port}\`;
8063
- const url = showAddressBar && shellPort !== null
8064
- ? \`http://127.0.0.1:\${shellPort}/browse.html?url=\${encodeURIComponent(direct)}\`
8065
- : direct;
7949
+ // Toolbar service windows (D13): the SAME native carrier URL applications
7950
+ // use — a windowOnly session whose toolbar webview (shell-served
7951
+ // toolbar page) sits above a content webview loading the verified
7952
+ // service URL directly (no iframe wrapper, no bridge on the content
7953
+ // side, no embedding-policy consultation). Direct service windows keep
7954
+ // the D13 native sync defaults unchanged.
7955
+ if (toolbarMode && shellPort !== null) {
7956
+ const win = tray.extend(WebviewExt).createWebviewWindow({
7957
+ windowOnly: true,
7958
+ width: config.window.width,
7959
+ height: config.window.height,
7960
+ title: baseTitle,
7961
+ style: { appMode: true, autoHide: false, keepOnTop: false },
7962
+ ...devtools,
7963
+ });
7964
+ await win.show().catch(() => {});
7965
+ await attachToolbarCarrier(win, {
7966
+ toolbarUrl: \`http://127.0.0.1:\${shellPort}/toolbar.html\`,
7967
+ contentUrl: direct,
7968
+ titleFollows,
7969
+ log: logNote,
7970
+ });
7971
+ serviceWindows.set(port, { win, detached: false });
7972
+ return;
7973
+ }
8066
7974
  const win = tray.extend(WebviewExt).createWebviewWindow({
8067
- url,
7975
+ url: direct,
8068
7976
  width: config.window.width,
8069
7977
  height: config.window.height,
8070
7978
  title: baseTitle,
8071
7979
  style: { appMode: true, autoHide: false, keepOnTop: false },
8072
7980
  ...devtools,
8073
- ...(showAddressBar || !titleFollows ? {} : { titleSync: { documentToWindow: true } }),
8074
- ...(showAddressBar || !iconFollows ? {} : { iconSync: { faviconToWindow: true } }),
7981
+ ...(!titleFollows ? {} : { titleSync: { documentToWindow: true } }),
7982
+ ...(!iconFollows ? {} : { iconSync: { faviconToWindow: true } }),
8075
7983
  });
8076
7984
  serviceWindows.set(port, { win, detached: false });
8077
7985
  await win.show().catch(() => {});
8078
7986
  };
8079
7987
 
8080
- // D13 sync defaults (address-bar-less service windows): the title follows
8081
- // the document one-way; runtime favicon following is opt-in only.
8082
- const titleFollows = config.window.titleFollowsDocument !== false;
8083
- const iconFollows = config.window.iconFollowsDocument === true;
8084
-
8085
7988
  if (showTerminal) {
8086
7989
  await ensureTerminalWindow();
8087
7990
  }
@@ -8239,7 +8142,7 @@ main().catch(async (error) => {
8239
8142
  };
8240
8143
  //#endregion
8241
8144
  //#region packages/core/src/url-entry-template.ts
8242
- /** The generated URL app entry: owns tray + one direct or wrapped window. */
8145
+ /** The generated URL app entry: owns tray + one direct or toolbar-carrier window. */
8243
8146
  const createUrlEntrySource = (config) => {
8244
8147
  if (config.url === void 0) throw new Error("URL entry template requires a url source");
8245
8148
  return `#!/usr/bin/env node
@@ -8251,7 +8154,7 @@ import { dirname, resolve } from "node:path";
8251
8154
  import { fileURLToPath } from "node:url";
8252
8155
 
8253
8156
  import { createTray } from "opentray";
8254
- import { WebviewExt } from "@opentray/ext-webview";
8157
+ import { WebviewExt, column, fixed, grow } from "@opentray/ext-webview";
8255
8158
 
8256
8159
  const PROJECT_DIR = dirname(fileURLToPath(import.meta.url));
8257
8160
 
@@ -8275,8 +8178,9 @@ const iconFollows = config.window.iconFollowsDocument === true;
8275
8178
  const appLogPath = resolve(PROJECT_DIR, "app.log");
8276
8179
  await mkdir(dirname(appLogPath), { recursive: true });
8277
8180
  const logSink = appendFile.bind(undefined, appLogPath);
8181
+ const logNote = (message) => { void logSink("[create-opentray] " + message + "\\n", "utf8"); };
8278
8182
 
8279
- const main = async () => {
8183
+ ${toolbarCarrierSource()}const main = async () => {
8280
8184
  let appIcon;
8281
8185
  try {
8282
8186
  const manifest = JSON.parse(await readFile(resolve(PROJECT_DIR, "app-icon", "app-icon.json"), "utf8"));
@@ -8326,42 +8230,59 @@ const main = async () => {
8326
8230
  // must not request devtools at all — default windows are not inspectable.
8327
8231
  const devtools = config.developerMode === true ? { devtools: true } : {};
8328
8232
 
8329
- // Toolbar mode (D12): the shell host serves the shared address-bar wrapper;
8330
- // no PTY is registered because a URL app supervises nothing. The wrapper
8331
- // carries neither sync option its document metadata is not the target's.
8233
+ // Toolbar shell host (D12): the shell server serves the toolbar page (and
8234
+ // nothing else for a URL app its navigation surface is channel-only);
8235
+ // no PTY is registered because a URL app supervises nothing. This is a
8236
+ // LOCAL transport dependency only — never an embedding-policy fallback
8237
+ // (embedding policy is not consulted at all, D14).
8332
8238
  let shellPort = null;
8333
8239
  if (toolbarMode) {
8334
8240
  const shellApi = await import("./app-shell-server.mjs");
8335
8241
  shellPort = await shellApi.listenShell();
8336
8242
  if (shellPort === null) {
8337
- await logSink("[create-opentray] toolbar shell host failed to listen; falling back to the direct window\\n", "utf8");
8243
+ await logSink("[create-opentray] toolbar shell host failed to listen; opening the direct window\\n", "utf8");
8338
8244
  }
8339
8245
  }
8340
- const toolbarUrl = toolbarMode && shellPort !== null
8341
- ? \`http://127.0.0.1:\${shellPort}/browse.html?url=\${encodeURIComponent(config.url)}\`
8342
- : null;
8246
+ const toolbarReady = toolbarMode && shellPort !== null;
8343
8247
 
8248
+ // Toolbar carrier (D12/D13): windowOnly session composed of the toolbar +
8249
+ // content webviews through attachToolbarCarrier below. Direct mode keeps
8250
+ // the single-webview window with the native sync defaults (D13): the title
8251
+ // follows the document one-way; icon following is opt-in.
8344
8252
  const window = tray.extend(WebviewExt).createWebviewWindow({
8345
- url: toolbarUrl ?? config.url,
8253
+ ...(toolbarReady ? { windowOnly: true } : { url: config.url }),
8346
8254
  width: config.window.width,
8347
8255
  height: config.window.height,
8348
8256
  title: config.appName,
8349
8257
  style: { appMode: true, autoHide: false, keepOnTop: false },
8350
8258
  ...devtools,
8351
- // D13 sync defaults project only onto the DIRECT window (toolbar windows
8352
- // set neither: one-way title following; icon following is opt-in).
8353
- ...(toolbarUrl === null
8354
- ? {
8355
- ...(titleFollows ? { titleSync: { documentToWindow: true } } : {}),
8356
- ...(iconFollows ? { iconSync: { faviconToWindow: true } } : {}),
8357
- }
8358
- : {}),
8259
+ ...(toolbarReady ? {} : {
8260
+ ...(titleFollows ? { titleSync: { documentToWindow: true } } : {}),
8261
+ ...(iconFollows ? { iconSync: { faviconToWindow: true } } : {}),
8262
+ }),
8359
8263
  });
8360
8264
  await window.show().catch(() => {});
8361
8265
 
8362
- // Tray reload (D11): reload the page in place through the evaluate channel.
8266
+ let content = null;
8267
+ if (toolbarReady) {
8268
+ ({ content } = await attachToolbarCarrier(window, {
8269
+ toolbarUrl: \`http://127.0.0.1:\${shellPort}/toolbar.html\`,
8270
+ contentUrl: config.url,
8271
+ titleFollows,
8272
+ log: logNote,
8273
+ }));
8274
+ }
8275
+
8276
+ // Tray reload (D11): toolbar mode reloads the CONTENT webview natively
8277
+ // (navigate to its current URL — no wrapper evaluate); direct mode reloads
8278
+ // the page in place through the evaluate channel. The app never restarts.
8363
8279
  const reload = async () => {
8364
8280
  try {
8281
+ if (content !== null) {
8282
+ const { url } = await content.getUrl();
8283
+ await content.navigate(url);
8284
+ return;
8285
+ }
8365
8286
  await window.evaluate("location.reload()");
8366
8287
  } catch { /* window gone */ }
8367
8288
  };
@@ -8594,14 +8515,14 @@ const writeScaffold = async (options) => {
8594
8515
  };
8595
8516
  };
8596
8517
  /**
8597
- * Locate the adapter-staged shell UI (terminal.html/browse.html + assets).
8518
+ * Locate the adapter-staged shell UI (terminal.html/toolbar.html + assets).
8598
8519
  * Layouts, in order:
8599
8520
  * - published create-opentray: `dist/shell` beside the package root (resolved
8600
8521
  * through the package self-reference; core is bundled into that dist);
8601
8522
  * - source checkout: `<create root>/dist/shell` (built) or the create-webui
8602
8523
  * vite output one workspace up.
8603
8524
  * Undefined means "no staged assets found" — the entry still runs; only the
8604
- * optional terminal/address-bar pages would 404.
8525
+ * optional terminal/toolbar pages would 404.
8605
8526
  */
8606
8527
  const resolveBundledShellAssetsDir = async () => {
8607
8528
  const moduleDir = dirname(fileURLToPath(import.meta.url));
@@ -13874,7 +13795,8 @@ const readWizardProjectConfig = async (projectDir) => {
13874
13795
  const url = typeof raw.url === "string" ? raw.url : void 0;
13875
13796
  const window = isRecord(raw.window) ? {
13876
13797
  width: Number.isFinite(raw.window.width) ? Number(raw.window.width) : 1200,
13877
- height: Number.isFinite(raw.window.height) ? Number(raw.window.height) : 800
13798
+ height: Number.isFinite(raw.window.height) ? Number(raw.window.height) : 800,
13799
+ ...raw.window.toolbar === true ? { toolbar: true } : {}
13878
13800
  } : {
13879
13801
  width: 1200,
13880
13802
  height: 800
@@ -14793,7 +14715,6 @@ const createWizardSession = (options) => {
14793
14715
  let currentTokens = [];
14794
14716
  let currentCommand = "";
14795
14717
  let urlSource;
14796
- let urlFrameEmbeddable;
14797
14718
  let scrapedTitle;
14798
14719
  let interactive = true;
14799
14720
  let uiLocale = "zh-CN";
@@ -14820,7 +14741,7 @@ const createWizardSession = (options) => {
14820
14741
  pm: false,
14821
14742
  force: false,
14822
14743
  showStartupTerminal: false,
14823
- showAddressBar: false,
14744
+ toolbar: false,
14824
14745
  imageSmoothingEnabled: false,
14825
14746
  developerMode: false
14826
14747
  };
@@ -14833,7 +14754,7 @@ const createWizardSession = (options) => {
14833
14754
  trayIconPath: "",
14834
14755
  force: options.force === true,
14835
14756
  showStartupTerminal: false,
14836
- showAddressBar: false,
14757
+ toolbar: false,
14837
14758
  imageSmoothingEnabled: true,
14838
14759
  developerMode: false,
14839
14760
  pm: options.packageManager ?? detectPackageManager([], process.env.npm_config_user_agent)
@@ -15179,7 +15100,6 @@ const createWizardSession = (options) => {
15179
15100
  const trimmed = url.trim();
15180
15101
  if (trimmed.length === 0) {
15181
15102
  urlSource = void 0;
15182
- urlFrameEmbeddable = void 0;
15183
15103
  scrapedTitle = void 0;
15184
15104
  iconCandidates = [];
15185
15105
  iconPort = void 0;
@@ -15217,7 +15137,6 @@ const createWizardSession = (options) => {
15217
15137
  currentIconPath = scraped.ok ? scraped.icons[0]?.path : void 0;
15218
15138
  currentIconUrl = scraped.ok ? scraped.icons[0]?.url : void 0;
15219
15139
  scrapedTitle = scraped.ok ? scraped.title : void 0;
15220
- urlFrameEmbeddable = scraped.ok ? scraped.frameEmbeddable : void 0;
15221
15140
  stopTimers();
15222
15141
  setState("discovered");
15223
15142
  emit({
@@ -15292,7 +15211,7 @@ const createWizardSession = (options) => {
15292
15211
  ...touched.force ? { force: form.force } : { force: options.force === true },
15293
15212
  ...touched.trayIconPath ? { trayIconPath: form.trayIconPath } : { trayIconPath: "" },
15294
15213
  ...touched.showStartupTerminal ? { showStartupTerminal: form.showStartupTerminal } : { showStartupTerminal: false },
15295
- ...touched.showAddressBar ? { showAddressBar: form.showAddressBar } : { showAddressBar: false },
15214
+ ...touched.toolbar ? { toolbar: form.toolbar } : { toolbar: false },
15296
15215
  ...touched.imageSmoothingEnabled ? { imageSmoothingEnabled: form.imageSmoothingEnabled } : { imageSmoothingEnabled: true },
15297
15216
  ...touched.developerMode ? { developerMode: form.developerMode } : { developerMode: false },
15298
15217
  pm: options.packageManager ?? detectPackageManager([], process.env.npm_config_user_agent)
@@ -15640,7 +15559,7 @@ const createWizardSession = (options) => {
15640
15559
  window: {
15641
15560
  width: 1200,
15642
15561
  height: 800,
15643
- ...frozen.showAddressBar === true && urlFrameEmbeddable !== false ? { toolbar: true } : {},
15562
+ ...frozen.toolbar === true ? { toolbar: true } : {},
15644
15563
  titleFollowsDocument: true,
15645
15564
  iconFollowsDocument: false
15646
15565
  }
@@ -15654,6 +15573,7 @@ const createWizardSession = (options) => {
15654
15573
  window: {
15655
15574
  width: 1200,
15656
15575
  height: 800,
15576
+ ...frozen.toolbar === true ? { toolbar: true } : {},
15657
15577
  titleFollowsDocument: true,
15658
15578
  iconFollowsDocument: false
15659
15579
  }
@@ -15727,12 +15647,6 @@ const createWizardSession = (options) => {
15727
15647
  run = void 0;
15728
15648
  }
15729
15649
  if (urlSource !== void 0) {
15730
- const toolbarWanted = frozen.showAddressBar === true;
15731
- const toolbar = toolbarWanted && urlFrameEmbeddable !== false;
15732
- if (toolbarWanted && !toolbar) emit({
15733
- type: "materialize-log",
15734
- message: `warning: ${urlSource} forbids iframe embedding (X-Frame-Options / CSP frame-ancestors); the address bar falls back to the direct window`
15735
- });
15736
15650
  try {
15737
15651
  result = await materialize({
15738
15652
  config: {
@@ -15744,7 +15658,7 @@ const createWizardSession = (options) => {
15744
15658
  window: {
15745
15659
  width: 1200,
15746
15660
  height: 800,
15747
- ...toolbar ? { toolbar: true } : {},
15661
+ ...frozen.toolbar === true ? { toolbar: true } : {},
15748
15662
  titleFollowsDocument: true,
15749
15663
  iconFollowsDocument: false
15750
15664
  },
@@ -15818,6 +15732,7 @@ const createWizardSession = (options) => {
15818
15732
  window: {
15819
15733
  width: 1200,
15820
15734
  height: 800,
15735
+ ...frozen.toolbar === true ? { toolbar: true } : {},
15821
15736
  titleFollowsDocument: true,
15822
15737
  iconFollowsDocument: false
15823
15738
  },
@@ -15830,10 +15745,7 @@ const createWizardSession = (options) => {
15830
15745
  ...frozen.iconScale === void 0 ? {} : { iconScale: frozen.iconScale },
15831
15746
  ...currentTrayIconPath === void 0 ? {} : { trayIconSourcePath: currentTrayIconPath },
15832
15747
  ...trayIconIsSolid ? { trayIconIsSolid: true } : {},
15833
- shell: {
15834
- showTerminal: frozen.showStartupTerminal,
15835
- showAddressBar: frozen.showAddressBar
15836
- },
15748
+ shell: { showTerminal: frozen.showStartupTerminal },
15837
15749
  ...frozen.imageSmoothingEnabled === false ? { imageSmoothingEnabled: false } : {},
15838
15750
  ...frozen.developerMode === true ? { developerMode: true } : {},
15839
15751
  packageManager: frozen.pm,
@@ -16037,6 +15949,7 @@ const handleWorkbenchApi = async (request) => {
16037
15949
  window: {
16038
15950
  width: entry.config.window.width,
16039
15951
  height: entry.config.window.height,
15952
+ ...entry.config.window.toolbar === true ? { toolbar: true } : {},
16040
15953
  titleFollowsDocument: true,
16041
15954
  iconFollowsDocument: false
16042
15955
  },
@@ -16204,6 +16117,7 @@ const handleWorkbenchApi = async (request) => {
16204
16117
  window: {
16205
16118
  width: entry.config.window.width,
16206
16119
  height: entry.config.window.height,
16120
+ ...entry.config.window.toolbar === true ? { toolbar: true } : {},
16207
16121
  titleFollowsDocument: true,
16208
16122
  iconFollowsDocument: false
16209
16123
  },
@@ -16913,7 +16827,7 @@ const handleApi = async (pathname, request, response, session, preReadBody) => {
16913
16827
  if (typeof body.iconScale === "number" && body.iconScale >= .5 && body.iconScale <= .95) patch.iconScale = body.iconScale;
16914
16828
  for (const key of [
16915
16829
  "showStartupTerminal",
16916
- "showAddressBar",
16830
+ "toolbar",
16917
16831
  "imageSmoothingEnabled",
16918
16832
  "developerMode",
16919
16833
  "force"
@@ -17516,17 +17430,11 @@ const runCreate = async (args, context) => {
17516
17430
  }
17517
17431
  let enrichment;
17518
17432
  let presetIconPath;
17519
- let toolbarRequested = args.toolbar === true;
17520
- if (args.url !== void 0 && args.scrape !== false && (args.appName === void 0 || args.appIcon === void 0 || toolbarRequested)) {
17433
+ if (args.url !== void 0 && args.scrape !== false && (args.appName === void 0 || args.appIcon === void 0)) {
17521
17434
  const presets = await deriveUrlPresets(args.url);
17522
17435
  enrichment = presets;
17523
17436
  presetIconPath = presets.appIconPath;
17524
17437
  if (presets.appName !== void 0 || presets.appIconPath !== void 0) emitProgress(`scraped defaults from ${args.url}: ${[presets.appName !== void 0 ? "title" : void 0, presets.appIconPath !== void 0 ? "favicon" : void 0].filter(Boolean).join(" + ")} (defaults; explicit flags win)`, context.streams, args.json === true);
17525
- if (toolbarRequested && presets.frameEmbeddable === false) {
17526
- toolbarRequested = false;
17527
- delete flags.toolbar;
17528
- emitProgress(`warning: ${args.url} forbids iframe embedding (X-Frame-Options / CSP frame-ancestors); falling back to the direct window`, context.streams, args.json === true);
17529
- }
17530
17438
  }
17531
17439
  const compiled = await compileDesiredConfig(flags, args.config, cwd, enrichment);
17532
17440
  if (!compiled.ok) {
@@ -17651,7 +17559,7 @@ const createCommand = (context) => ({
17651
17559
  describe: "window size <width>x<height> (default 1200x800)"
17652
17560
  }).option("toolbar", {
17653
17561
  type: "boolean",
17654
- describe: "URL applications: host the address-bar wrapper (back/forward/reload toolbar)"
17562
+ describe: "compose the native navigation toolbar over the target/service page (URL and command applications; back/forward/reload/address bar)"
17655
17563
  }).option("title-follow", {
17656
17564
  type: "boolean",
17657
17565
  describe: "window title follows document.title (default true; negate with --no-title-follow)"
@@ -18361,6 +18269,33 @@ const openBrowser = async (url) => {
18361
18269
  windowsHide: true
18362
18270
  }).unref();
18363
18271
  };
18272
+ /** Draft form values carry fields across versions — forward only the
18273
+ * known-safe, type-checked subset. Retired fields (the legacy
18274
+ * showAddressBar, replaced by `toolbar` in add-webview-orchestration D13)
18275
+ * are ignored here: a stale draft never resurrects a removed input.
18276
+ * Exported for tests alongside parseWizardCli. */
18277
+ const normalizeDraftForm = (raw) => {
18278
+ if (typeof raw !== "object" || raw === null) return;
18279
+ const source = raw;
18280
+ const patch = {};
18281
+ for (const key of [
18282
+ "appId",
18283
+ "appName",
18284
+ "iconPath",
18285
+ "trayIconPath"
18286
+ ]) if (typeof source[key] === "string") patch[key] = source[key];
18287
+ if (source.pm === "npm" || source.pm === "pnpm" || source.pm === "bun") patch.pm = source.pm;
18288
+ if (source.iconBackground === "black" || source.iconBackground === "white" || source.iconBackground === "transparent") patch.iconBackground = source.iconBackground;
18289
+ if (typeof source.iconScale === "number" && source.iconScale >= .5 && source.iconScale <= .95) patch.iconScale = source.iconScale;
18290
+ for (const key of [
18291
+ "showStartupTerminal",
18292
+ "toolbar",
18293
+ "imageSmoothingEnabled",
18294
+ "developerMode",
18295
+ "force"
18296
+ ]) if (typeof source[key] === "boolean") patch[key] = source[key];
18297
+ return Object.keys(patch).length > 0 ? patch : void 0;
18298
+ };
18364
18299
  const runWebAdapter = async (options) => {
18365
18300
  ensureLoopbackNoProxy();
18366
18301
  const invocationDir = process.env.INIT_CWD || process.cwd();
@@ -18388,9 +18323,10 @@ const runWebAdapter = async (options) => {
18388
18323
  const raw = await readFile(join(tmpdir(), `create-opentray-draft-${port ?? 0}.json`), "utf8");
18389
18324
  const parsed = JSON.parse(raw);
18390
18325
  const commandOptions = normalizeDraftCommandOptions(parsed.commandOptions);
18391
- if (parsed.form === void 0 && parsed.command === void 0 && commandOptions === void 0) return;
18326
+ const form = parsed.form === void 0 ? void 0 : normalizeDraftForm(parsed.form);
18327
+ if (form === void 0 && parsed.command === void 0 && commandOptions === void 0) return;
18392
18328
  return {
18393
- form: parsed.form,
18329
+ form,
18394
18330
  command: parsed.command,
18395
18331
  commandOptions
18396
18332
  };
@@ -18467,6 +18403,6 @@ if (isMainModule()) main(process.argv.slice(2)).then((code) => {
18467
18403
  process.exitCode = code;
18468
18404
  });
18469
18405
  //#endregion
18470
- export { resolveOnPath as A, parseNetstatPorts as C, waitForTcpPort as D, verifyHttpService as E, toProjectDirectoryName as F, __commonJSMin$1 as I, deriveDefaultAppId as M, deriveDefaultAppName as N, parseShebangInterpreter as O, isValidAppId as P, parseLsofPorts as S, serviceUrl as T, faviconCandidateSize as _, isAuthorized as a, scrapeService as b, openMaterializedApp as c, expectedDarwinBundlePath as d, isDirectoryOccupied$1 as f, extractTitle as g, extractFaviconCandidates as h, createWizardServer as i, tokenizeCommandLine as j, resolveLaunchVector as k, pinningHint as l, writeScaffold as m, openBrowser as n, isLoopbackHost as o, materialize as p, parseWizardCli as r, createWizardSession as s, main as t, detectPackageManager as u, rankFaviconCandidates as v, parsePowerShellPorts as w, createPortDiscovery as x, resolveFaviconUrl as y };
18406
+ export { resolveLaunchVector as A, parseLsofPorts as C, verifyHttpService as D, serviceUrl as E, isValidAppId as F, toProjectDirectoryName as I, tokenizeCommandLine as M, deriveDefaultAppId as N, waitForTcpPort as O, deriveDefaultAppName as P, createPortDiscovery as S, parsePowerShellPorts as T, extractTitle as _, createWizardServer as a, resolveFaviconUrl as b, createWizardSession as c, detectPackageManager as d, expectedDarwinBundlePath as f, extractFaviconCandidates as g, writeScaffold as h, parseWizardCli as i, resolveOnPath as j, parseShebangInterpreter as k, openMaterializedApp as l, materialize as m, normalizeDraftForm as n, isAuthorized as o, isDirectoryOccupied$1 as p, openBrowser as r, isLoopbackHost as s, main as t, pinningHint as u, faviconCandidateSize as v, parseNetstatPorts as w, scrapeService as x, rankFaviconCandidates as y };
18471
18407
 
18472
- //# sourceMappingURL=bin-BQb2Bjcu.mjs.map
18408
+ //# sourceMappingURL=bin-Bz16rwKs.mjs.map