helixlife-v5-cli 1.2.8 → 1.2.9

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 (46) hide show
  1. package/README.md +33 -58
  2. package/helix-cli.js +7 -2
  3. package/lib/.cli.runtime.generated.json +10 -1
  4. package/lib/automation-file-chooser-guard.js +182 -0
  5. package/lib/automation-overlay.init.js +332 -68
  6. package/lib/browser-css-cache-guard.init-page.js +49 -0
  7. package/lib/browser-window-maximize.init-page.js +64 -0
  8. package/lib/cli.js +316 -14
  9. package/lib/helix-reload-bypass-cache.run-code.js +7 -0
  10. package/lib/helix-styles-guard.js +77 -0
  11. package/lib/hide-nested-skills.js +120 -0
  12. package/lib/native-mode-patch.js +288 -0
  13. package/lib/postinstall.js +39 -0
  14. package/lib/profile-lock-guard.js +187 -0
  15. package/lib/runtime-config.js +90 -0
  16. package/lib/token-file.js +189 -0
  17. package/lib/visual-page-wrap.js +311 -0
  18. package/lib/visual.js +255 -86
  19. package/package.json +9 -21
  20. package/scripts/README.md +81 -0
  21. package/scripts/{analysis-detail-upload-file.js → analysis/detail-upload-file.js} +1 -1
  22. package/scripts/{jigsaw-inspect-dom.js → analysis/jigsaw-inspect-dom.js} +15 -15
  23. package/scripts/{jigsaw-verify-enumerate.js → analysis/jigsaw-verify-enumerate.js} +1 -1
  24. package/scripts/common/.gitkeep +0 -0
  25. package/scripts/{chrome-pdf-enumerate-a11y.js → edu/chrome-pdf-enumerate-a11y.js} +1 -1
  26. package/scripts/{chrome-pdf-goto-page.js → edu/chrome-pdf-goto-page.js} +1 -1
  27. package/scripts/{chrome-pdf-zoom-out.js → edu/chrome-pdf-zoom-out.js} +1 -1
  28. package/scripts/{hover-partial-chapter-match.js → edu/hover-partial-chapter-match.js} +1 -1
  29. package/scripts/maintainer/strip-extra-skills.ps1 +27 -0
  30. package/scripts/workbench/jova/outline-edit-root-title.js +33 -0
  31. package/scripts/workbench/jova/outline-keyword-edit.js +122 -0
  32. package/scripts/workbench/jova/outline-ops.js +344 -0
  33. package/scripts/workbench/jova/outline-probe-hover.js +56 -0
  34. package/scripts/workbench/jova/proofread-ai-rewrite-section.js +94 -0
  35. package/scripts/workbench/jova/proofread-ops.js +261 -0
  36. package/scripts/workbench/jova/writing-ops.js +376 -0
  37. package/scripts/workbench/jova/writing-proofread-abstract.js +103 -0
  38. package/lib/cli.runtime.config.json +0 -5
  39. package/references/vip.helixlife.cn.site-ops.md +0 -1469
  40. package/scripts/workbench-jova-outline-regenerate-node.js +0 -173
  41. package/scripts/workbench-jova-proofread-section.js +0 -262
  42. package/scripts/workbench-jova-writing-tool.js +0 -308
  43. /package/scripts/{analysis-detail-upload-both.js → analysis/detail-upload-both.js} +0 -0
  44. /package/scripts/{jigsaw-verify-bio-tools.js → analysis/jigsaw-verify-bio-tools.js} +0 -0
  45. /package/scripts/{jigsaw-verify-drag-tool.js → analysis/jigsaw-verify-drag-tool.js} +0 -0
  46. /package/scripts/{jigsaw-verify-reference-lines.js → analysis/jigsaw-verify-reference-lines.js} +0 -0
@@ -7,23 +7,51 @@
7
7
  (() => {
8
8
  if (window.__helix_visual__) return;
9
9
 
10
- const VERSION = '1.0.0';
10
+ const VERSION = "1.0.0";
11
11
  const VISUAL_DEFAULTS = {
12
12
  enabled: true,
13
13
  durationMs: 420,
14
+ /** true:HUD 与高亮不自动消失(改样式/调试);false:操作结束后按 durationMs 消失 */
15
+ persist: false,
14
16
  };
15
- const VISUAL_STYLE_ID = '__helix_visual_style__';
16
- const VISUAL_LAYER_ID = '__helix_visual_layer__';
17
- const VISUAL_HUD_ID = '__helix_visual_hud__';
17
+ const VISUAL_STYLE_ID = "__helix_visual_style__";
18
+ const VISUAL_LAYER_ID = "__helix_visual_layer__";
19
+ const VISUAL_HUD_ID = "__helix_visual_hud__";
18
20
  const VISUAL_TONE_MAP = {
19
- pending: { border: '#faad14', fill: 'rgba(250, 173, 20, 0.16)', pill: '#ad6800', text: '#fffbe6' },
20
- success: { border: '#52c41a', fill: 'rgba(82, 196, 26, 0.14)', pill: '#237804', text: '#f6ffed' },
21
- danger: { border: '#ff4d4f', fill: 'rgba(255, 77, 79, 0.14)', pill: '#a8071a', text: '#fff1f0' },
22
- info: { border: '#1677ff', fill: 'rgba(22, 119, 255, 0.14)', pill: '#0958d9', text: '#f0f5ff' },
21
+ pending: {
22
+ border: "#faad14",
23
+ fill: "rgba(250, 173, 20, 0.16)",
24
+ pill: "#ad6800",
25
+ text: "#fffbe6",
26
+ statusDot: "#FFA406",
27
+ },
28
+ success: {
29
+ border: "#9747ff",
30
+ fill: "rgba(151, 71, 255, 0.14)",
31
+ pill: "#9747ff",
32
+ text: "#f6ffed",
33
+ statusDot: "#9747ff",
34
+ },
35
+ danger: {
36
+ border: "#FE2C55",
37
+ fill: "rgba(254, 44, 85, 0.14)",
38
+ pill: "#FE2C55",
39
+ text: "#ffe9ee",
40
+ statusDot: "#FE2C55",
41
+ },
42
+ info: {
43
+ border: "#6546e4",
44
+ fill: "rgba(101, 70, 228, 0.14)",
45
+ pill: "#6546e4",
46
+ text: "#f0f5ff",
47
+ statusDot: "#6546e4",
48
+ },
23
49
  };
24
50
  const visualState = {
25
51
  config: { ...VISUAL_DEFAULTS },
26
52
  hudTimer: null,
53
+ /** 当前持久选中框(select / 脚本步骤高亮) */
54
+ pinnedBox: null,
27
55
  };
28
56
 
29
57
  function clamp(n, min, max) {
@@ -38,16 +66,25 @@
38
66
 
39
67
  function normalizeVisualOptions(options = {}) {
40
68
  const base = { ...visualState.config };
41
- if (!options || typeof options !== 'object') return base;
42
- if (typeof options.enabled === 'boolean') base.enabled = options.enabled;
43
- if (options.durationMs != null) base.durationMs = normalizeDuration(options.durationMs, base.durationMs);
69
+ if (!options || typeof options !== "object") return base;
70
+ if (typeof options.enabled === "boolean") base.enabled = options.enabled;
71
+ if (typeof options.persist === "boolean") base.persist = options.persist;
72
+ if (options.durationMs != null)
73
+ base.durationMs = normalizeDuration(options.durationMs, base.durationMs);
44
74
  return base;
45
75
  }
46
76
 
77
+ function getDomHost() {
78
+ return document.body || document.documentElement;
79
+ }
80
+
47
81
  function ensureVisualRoot() {
82
+ const layerHost = getDomHost();
83
+ if (!layerHost) return null;
84
+ const styleParent = document.head || document.documentElement || layerHost;
48
85
  let style = document.getElementById(VISUAL_STYLE_ID);
49
86
  if (!style) {
50
- style = document.createElement('style');
87
+ style = document.createElement("style");
51
88
  style.id = VISUAL_STYLE_ID;
52
89
  style.textContent = `
53
90
  #${VISUAL_LAYER_ID}{
@@ -66,27 +103,100 @@
66
103
  .__helix_visual_badge{
67
104
  position:absolute;
68
105
  left:0;
69
- top:-28px;
106
+ top:-40px;
107
+ width: max-content;
70
108
  max-width:280px;
71
- padding:4px 10px;
72
- border-radius:999px;
73
- font:600 12px/1.2 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
74
- letter-spacing:.01em;
75
- white-space:nowrap;
76
- text-overflow:ellipsis;
109
+ display:inline-flex;
110
+ align-items:center;
111
+ gap:10px;
112
+ box-sizing:border-box;
113
+ padding:7px 10px;
114
+ border-radius:20px;
115
+ background:#19191E;
116
+ box-shadow:0 0 8px rgba(115, 66, 214, 0.8);
117
+ font-weight: 400;
118
+ font-size: 12px;
119
+ color: #EBEBEB;
120
+ }
121
+ .__helix_visual_badge::before{
122
+ content: '';
123
+ position: absolute;
124
+ inset: -1px;
125
+ background: linear-gradient(90deg, rgba(151, 71, 255, 1), rgba(101, 70, 228, 1));
126
+ z-index: -1;
127
+ border-radius: 20px;
128
+ padding: 0px;
129
+ }
130
+ .__helix_visual_badge_text{
77
131
  overflow:hidden;
78
- box-shadow:0 8px 24px rgba(0,0,0,.18);
132
+ text-overflow:ellipsis;
79
133
  }
80
- #${VISUAL_HUD_ID}{
134
+ .__helix_visual_badge_dot{
135
+ width:6px;
136
+ height:6px;
137
+ border-radius:100%;
138
+ flex-shrink:0;
139
+ }
140
+ #${VISUAL_HUD_ID},
141
+ .__helix_visual_hud_shell{
81
142
  position:fixed;
143
+ z-index: 99;
82
144
  top:16px;
83
145
  right:16px;
84
- max-width:360px;
85
- padding:10px 14px;
86
- border-radius:12px;
87
- box-shadow:0 12px 32px rgba(0,0,0,.22);
88
- font:600 13px/1.35 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
89
- white-space:pre-wrap;
146
+ width:340px;
147
+ min-height:98px;
148
+ box-sizing:border-box;
149
+ padding:1px;
150
+ border-radius:20px;
151
+ background:linear-gradient(90deg, rgba(151, 71, 255, 1), rgba(101, 70, 228, 1));
152
+ box-shadow:0 0 8px rgba(115, 66, 214, 0.8);
153
+ }
154
+ .__helix_visual_hud{
155
+ width:100%;
156
+ min-height:96px;
157
+ box-sizing:border-box;
158
+ border-radius:19px;
159
+ background:#19191E;
160
+ padding:16px 18px;
161
+ color:rgba(255,255,255,0.88);
162
+ font:500 13px/1.4 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
163
+ }
164
+ .__helix_visual_hud_head{
165
+ display:flex;
166
+ align-items:center;
167
+ justify-content:space-between;
168
+ gap:12px;
169
+ margin-bottom:10px;
170
+ }
171
+ .__helix_visual_hud_action{
172
+ font-weight:600;
173
+ font-size:16px;
174
+ color:#EBEBEB;
175
+ }
176
+ .__helix_visual_hud_status{
177
+ display:inline-flex;
178
+ align-items:center;
179
+ gap:10px;
180
+ font-size:12px;
181
+ font-weight:400;
182
+ color:#EBEBEB;
183
+ white-space:nowrap;
184
+ }
185
+ .__helix_visual_hud_dot{
186
+ width:6px;
187
+ height:6px;
188
+ border-radius:100%;
189
+ flex-shrink:0;
190
+ }
191
+ .__helix_visual_hud_target{
192
+ padding:7px 10px;
193
+ background:rgba(235,235,235,0.05);
194
+ border-radius:10px;
195
+ display:flex;
196
+ align-items:center;
197
+ font-weight:400;
198
+ font-size:12px;
199
+ color:#B2B1B1;
90
200
  }
91
201
  @keyframes __helix_visual_pulse{
92
202
  0%{transform:scale(.985);opacity:.2}
@@ -94,13 +204,13 @@
94
204
  100%{transform:scale(1);opacity:1}
95
205
  }
96
206
  `;
97
- (document.head || document.documentElement).appendChild(style);
207
+ styleParent.appendChild(style);
98
208
  }
99
209
  let layer = document.getElementById(VISUAL_LAYER_ID);
100
210
  if (!layer) {
101
- layer = document.createElement('div');
211
+ layer = document.createElement("div");
102
212
  layer.id = VISUAL_LAYER_ID;
103
- (document.body || document.documentElement).appendChild(layer);
213
+ layerHost.appendChild(layer);
104
214
  }
105
215
  return layer;
106
216
  }
@@ -110,74 +220,226 @@
110
220
  }
111
221
 
112
222
  function removeLater(el, durationMs) {
113
- if (!el) return;
223
+ if (!el || visualState.config.persist) return;
114
224
  const ms = normalizeDuration(durationMs);
115
225
  window.setTimeout(() => {
116
226
  if (el && el.parentNode) el.remove();
117
227
  }, ms);
118
228
  }
119
229
 
120
- function flashElement(el, options = {}) {
121
- const { tone = 'info', label = '', durationMs, inset = 0 } = options;
230
+ function appendBadge(box, spec, label, badgeTop) {
231
+ const badge = document.createElement("div");
232
+ badge.className = "__helix_visual_badge";
233
+ badge.style.top = badgeTop + "px";
234
+
235
+ const dot = document.createElement("span");
236
+ dot.className = "__helix_visual_badge_dot";
237
+ dot.style.background = spec.statusDot || spec.border;
238
+
239
+ const text = document.createElement("span");
240
+ text.className = "__helix_visual_badge_text";
241
+ text.textContent = label;
242
+
243
+ badge.appendChild(dot);
244
+ badge.appendChild(text);
245
+ box.appendChild(badge);
246
+ }
247
+
248
+ function createHighlightBox(layer, spec, label, rect) {
249
+ const box = document.createElement("div");
250
+ box.className = "__helix_visual_box";
251
+ box.style.left = rect.left + "px";
252
+ box.style.top = rect.top + "px";
253
+ box.style.width = rect.width + "px";
254
+ box.style.height = rect.height + "px";
255
+ // box.style.border = `2px solid ${spec.border}`;
256
+ // box.style.background = spec.fill;
257
+ // box.style.boxShadow = `0 0 0 1px ${spec.border}33, 0 12px 28px ${spec.border}22`;
258
+ box.style.border = "none";
259
+ box.style.boxShadow = "0 0 0 6px rgba(159, 71, 255, 0.2)";
260
+ if (label) {
261
+ appendBadge(box, spec, label, rect.top < 38 ? rect.height + 8 : -45);
262
+ }
263
+ layer.appendChild(box);
264
+ return box;
265
+ }
266
+
267
+ function populateHudCard(inner, options = {}) {
268
+ const {
269
+ action = "",
270
+ target = "",
271
+ status = "pending",
272
+ detail = "",
273
+ } = options;
274
+ const spec = toneSpec(status);
275
+ inner.replaceChildren();
276
+
277
+ const head = document.createElement("div");
278
+ head.className = "__helix_visual_hud_head";
279
+
280
+ const actionEl = document.createElement("span");
281
+ actionEl.className = "__helix_visual_hud_action";
282
+ actionEl.textContent = action;
283
+
284
+ const statusEl = document.createElement("span");
285
+ statusEl.className = "__helix_visual_hud_status";
286
+
287
+ const dot = document.createElement("span");
288
+ dot.className = "__helix_visual_hud_dot";
289
+ dot.style.background = spec.statusDot || spec.border;
290
+
291
+ const statusText = document.createElement("span");
292
+ statusText.textContent = detail;
293
+
294
+ statusEl.appendChild(dot);
295
+ statusEl.appendChild(statusText);
296
+ head.appendChild(actionEl);
297
+ head.appendChild(statusEl);
298
+ inner.appendChild(head);
299
+
300
+ const targetEl = document.createElement("div");
301
+ targetEl.className = "__helix_visual_hud_target";
302
+ const targetText = String(target || "").trim();
303
+ if (targetText) {
304
+ targetEl.textContent = targetText;
305
+ } else {
306
+ targetEl.textContent = "执行中…";
307
+ }
308
+ inner.appendChild(targetEl);
309
+ }
310
+
311
+ function createHudCard(options = {}) {
312
+ const shell = document.createElement("div");
313
+ shell.className = "__helix_visual_hud_shell";
314
+ const inner = document.createElement("div");
315
+ inner.className = "__helix_visual_hud";
316
+ populateHudCard(inner, options);
317
+ shell.appendChild(inner);
318
+ return shell;
319
+ }
320
+
321
+ function rectWithInset(rect, inset = 0) {
322
+ return {
323
+ left: Math.max(4, rect.left - inset),
324
+ top: Math.max(4, rect.top - inset),
325
+ width: Math.max(18, rect.width + inset * 2),
326
+ height: Math.max(18, rect.height + inset * 2),
327
+ };
328
+ }
329
+
330
+ function unpinSelection() {
331
+ if (visualState.pinnedBox && visualState.pinnedBox.parentNode) {
332
+ visualState.pinnedBox.remove();
333
+ }
334
+ visualState.pinnedBox = null;
335
+ }
336
+
337
+ /**
338
+ * 持久选中态:紫色边框 + 角标标签,并同步右上角 HUD(用于 run-code 脚本步骤)。
339
+ */
340
+ function selectElement(el, options = {}) {
341
+ const {
342
+ tone = "pending",
343
+ label = "",
344
+ action = "",
345
+ target = "",
346
+ detail = "",
347
+ inset = 4,
348
+ } = options;
122
349
  if (!el || !visualState.config.enabled) return false;
123
350
  const rect = el.getBoundingClientRect();
124
351
  if (!rect || rect.width <= 0 || rect.height <= 0) return false;
125
352
  const layer = ensureVisualRoot();
353
+ if (!layer) return false;
354
+ unpinSelection();
126
355
  const spec = toneSpec(tone);
127
- const box = document.createElement('div');
128
- box.className = '__helix_visual_box';
129
- box.style.left = Math.max(4, rect.left - inset) + 'px';
130
- box.style.top = Math.max(4, rect.top - inset) + 'px';
131
- box.style.width = Math.max(18, rect.width + inset * 2) + 'px';
132
- box.style.height = Math.max(18, rect.height + inset * 2) + 'px';
133
- box.style.border = `2px solid ${spec.border}`;
134
- box.style.background = spec.fill;
135
- box.style.boxShadow = `0 0 0 1px ${spec.border}33, 0 12px 28px ${spec.border}22`;
136
- if (label) {
137
- const badge = document.createElement('div');
138
- badge.className = '__helix_visual_badge';
139
- badge.textContent = label;
140
- badge.style.background = spec.pill;
141
- badge.style.color = spec.text;
142
- badge.style.top = (rect.top < 38 ? rect.height + 8 : -28) + 'px';
143
- box.appendChild(badge);
356
+ const box = createHighlightBox(
357
+ layer,
358
+ spec,
359
+ label,
360
+ rectWithInset(rect, inset),
361
+ );
362
+ box.dataset.helixPinned = "1";
363
+ visualState.pinnedBox = box;
364
+ if (action || target || detail || label) {
365
+ showHud({
366
+ action: action || "步骤",
367
+ target: target || label,
368
+ status: tone,
369
+ detail: detail || label || "执行中…",
370
+ });
144
371
  }
145
- layer.appendChild(box);
372
+ return true;
373
+ }
374
+
375
+ function flashElement(el, options = {}) {
376
+ const {
377
+ tone = "info",
378
+ label = "",
379
+ durationMs,
380
+ inset = 0,
381
+ action = "",
382
+ target = "",
383
+ detail = "",
384
+ } = options;
385
+ if (!el || !visualState.config.enabled) return false;
386
+ const rect = el.getBoundingClientRect();
387
+ if (!rect || rect.width <= 0 || rect.height <= 0) return false;
388
+ const layer = ensureVisualRoot();
389
+ if (!layer) return false;
390
+ const spec = toneSpec(tone);
391
+ const box = createHighlightBox(
392
+ layer,
393
+ spec,
394
+ label,
395
+ rectWithInset(rect, inset),
396
+ );
146
397
  removeLater(box, durationMs);
147
398
  return true;
148
399
  }
149
400
 
150
401
  function showHud(options = {}) {
151
- const { action = '', target = '', status = 'pending', detail = '', durationMs } = options;
402
+ const {
403
+ action = "",
404
+ target = "",
405
+ status = "pending",
406
+ detail = "",
407
+ durationMs,
408
+ } = options;
152
409
  if (!visualState.config.enabled) return false;
153
410
  const layer = ensureVisualRoot();
411
+ if (!layer) return false;
154
412
  let hud = document.getElementById(VISUAL_HUD_ID);
155
413
  if (!hud) {
156
- hud = document.createElement('div');
414
+ hud = createHudCard({ action, target, status, detail });
157
415
  hud.id = VISUAL_HUD_ID;
158
416
  layer.appendChild(hud);
417
+ } else {
418
+ const inner = hud.querySelector(".__helix_visual_hud");
419
+ if (inner) populateHudCard(inner, { action, target, status, detail });
159
420
  }
160
- const spec = toneSpec(status);
161
- const lines = [];
162
- if (action) lines.push(action);
163
- if (target) lines.push(target);
164
- if (detail) lines.push(detail);
165
- hud.textContent = lines.join('\n');
166
- hud.style.border = `1px solid ${spec.border}`;
167
- hud.style.background = spec.fill.replace(/0\.\d+\)$/, '0.92)');
168
- hud.style.color = spec.pill;
421
+ if (visualState.config.persist) return true;
169
422
  if (visualState.hudTimer) clearTimeout(visualState.hudTimer);
170
- visualState.hudTimer = window.setTimeout(() => {
171
- if (hud && hud.parentNode) hud.remove();
172
- visualState.hudTimer = null;
173
- }, normalizeDuration(durationMs, Math.max(900, visualState.config.durationMs * 2)));
423
+ visualState.hudTimer = window.setTimeout(
424
+ () => {
425
+ if (hud && hud.parentNode) hud.remove();
426
+ visualState.hudTimer = null;
427
+ },
428
+ normalizeDuration(
429
+ durationMs,
430
+ Math.max(900, visualState.config.durationMs * 2),
431
+ ),
432
+ );
174
433
  return true;
175
434
  }
176
435
 
177
436
  function cleanupVisualArtifacts() {
437
+ unpinSelection();
178
438
  const layer = document.getElementById(VISUAL_LAYER_ID);
179
439
  if (!layer) return;
180
- Array.from(layer.querySelectorAll('.__helix_visual_box')).forEach((el) => el.remove());
440
+ Array.from(layer.querySelectorAll(".__helix_visual_box")).forEach((el) =>
441
+ el.remove(),
442
+ );
181
443
  const hud = document.getElementById(VISUAL_HUD_ID);
182
444
  if (hud) hud.remove();
183
445
  if (visualState.hudTimer) {
@@ -187,7 +449,7 @@
187
449
  }
188
450
 
189
451
  function setVisualOptions(options = {}) {
190
- if (!options || typeof options !== 'object') {
452
+ if (!options || typeof options !== "object") {
191
453
  visualState.config = { ...VISUAL_DEFAULTS };
192
454
  cleanupVisualArtifacts();
193
455
  return { ok: true, data: { ...visualState.config } };
@@ -201,6 +463,8 @@
201
463
  version: () => VERSION,
202
464
  setVisualOptions,
203
465
  flash: flashElement,
466
+ select: selectElement,
467
+ unpin: unpinSelection,
204
468
  showHud,
205
469
  cleanup: cleanupVisualArtifacts,
206
470
  __meta: { version: VERSION, loadedAt: new Date().toISOString() },
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const { HELIX_HOST_RE } = require('./helix-styles-guard');
4
+
5
+ const GUARD_KEY = '__helixCssCacheGuard__';
6
+
7
+ async function hasBrokenHelixStylesheet(page) {
8
+ return page.evaluate(() => {
9
+ const links = [...document.querySelectorAll('link[rel="stylesheet"]')];
10
+ const appCss = links.filter((l) => l.href.includes('/static/css/'));
11
+ if (appCss.length === 0) return false;
12
+ return appCss.some((l) => {
13
+ try {
14
+ return !l.sheet;
15
+ } catch {
16
+ return true;
17
+ }
18
+ });
19
+ });
20
+ }
21
+
22
+ async function cssCacheGuard({ page }) {
23
+ if (page[GUARD_KEY]) return;
24
+ page[GUARD_KEY] = true;
25
+
26
+ const maybeFix = async () => {
27
+ try {
28
+ const url = page.url();
29
+ if (!HELIX_HOST_RE.test(url)) return;
30
+ const broken = await hasBrokenHelixStylesheet(page);
31
+ if (!broken) return;
32
+ if (page[`${GUARD_KEY}Reloaded`]) return;
33
+ page[`${GUARD_KEY}Reloaded`] = true;
34
+ await page.reload({ waitUntil: 'networkidle', bypassCache: true });
35
+ } catch {
36
+ // 页面已关闭或正在导航
37
+ }
38
+ };
39
+
40
+ page.on('load', () => {
41
+ void maybeFix();
42
+ });
43
+
44
+ if (page.url() && page.url() !== 'about:blank') {
45
+ await maybeFix();
46
+ }
47
+ }
48
+
49
+ module.exports = { default: cssCacheGuard };
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 有头会话启动后通过 CDP 将首个浏览器窗口最大化(配合 launchOptions --start-maximized)。
5
+ * initPage 在 Playwright CLI 每个新标签页/窗口初始化时调用;后续页面不再强制最大化,
6
+ * 新窗口则复制已有窗口的位置与尺寸,避免用户手动调整窗口后新开页又变全屏。
7
+ */
8
+
9
+ async function getWindowId(page) {
10
+ const cdp = await page.context().newCDPSession(page);
11
+ const { windowId } = await cdp.send('Browser.getWindowForTarget');
12
+ return { cdp, windowId };
13
+ }
14
+
15
+ async function maximizeBrowserWindow({ page }) {
16
+ try {
17
+ const context = page.context();
18
+ const pages = context.pages();
19
+
20
+ if (pages.length > 1) {
21
+ await syncWindowBoundsFromExisting(page, pages);
22
+ return;
23
+ }
24
+
25
+ const { cdp, windowId } = await getWindowId(page);
26
+ await cdp.send('Browser.setWindowBounds', {
27
+ windowId,
28
+ bounds: { windowState: 'maximized' },
29
+ });
30
+ } catch {
31
+ // Chromium channel unavailable or CDP unsupported — rely on --start-maximized.
32
+ }
33
+ }
34
+
35
+ /**
36
+ * 新标签/新窗口:同窗口内无需处理;独立 popup 窗口则复制参考窗口 bounds。
37
+ */
38
+ async function syncWindowBoundsFromExisting(page, pages) {
39
+ const refPage = pages.find((p) => p !== page && !p.isClosed()) ?? pages[0];
40
+ if (!refPage || refPage === page) return;
41
+
42
+ try {
43
+ const { windowId: newWindowId } = await getWindowId(page);
44
+ const { windowId: refWindowId } = await getWindowId(refPage);
45
+
46
+ if (newWindowId === refWindowId) return;
47
+
48
+ const cdpRef = await page.context().newCDPSession(refPage);
49
+ const { bounds } = await cdpRef.send('Browser.getWindowBounds', {
50
+ windowId: refWindowId,
51
+ });
52
+ if (!bounds) return;
53
+
54
+ const cdpNew = await page.context().newCDPSession(page);
55
+ await cdpNew.send('Browser.setWindowBounds', {
56
+ windowId: newWindowId,
57
+ bounds,
58
+ });
59
+ } catch {
60
+ // CDP unsupported or race during close — ignore.
61
+ }
62
+ }
63
+
64
+ module.exports = { default: maximizeBrowserWindow };