@xiaobuyu/nodesign 0.0.1 → 0.0.2
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 +156 -106
- package/bin/nodesign.js +0 -0
- package/package.json +1 -1
- package/server/api/README.md +45 -45
- package/server/api/_guard.js +75 -75
- package/server/api/admin.js +187 -187
- package/server/api/assets/docx-page.js +103 -103
- package/server/api/assets/helpers.js +22 -22
- package/server/api/assets/notes.js +144 -144
- package/server/api/assets.js +896 -896
- package/server/api/board.js +57 -57
- package/server/api/browse.js +138 -138
- package/server/api/canvas.js +444 -444
- package/server/api/chatai.js +324 -324
- package/server/api/exports/build-standalone.js +980 -980
- package/server/api/exports/cards.js +118 -118
- package/server/api/exports/docx-pdf.js +46 -46
- package/server/api/exports/export-page.js +142 -142
- package/server/api/exports/handoff.js +147 -147
- package/server/api/exports.js +690 -690
- package/server/api/instruction.js +75 -75
- package/server/api/me.js +116 -116
- package/server/api/memory.js +185 -185
- package/server/api/pending-changes.js +366 -366
- package/server/api/plugins.js +164 -164
- package/server/api/projects.js +220 -220
- package/server/api/publish.js +64 -64
- package/server/api/recent.js +73 -73
- package/server/api/sessions.js +589 -589
- package/server/api/skills.js +77 -77
- package/server/api/standalone-fit.js +168 -168
- package/server/api/turn-compose.js +180 -180
- package/server/api/turn-inflight.js +44 -44
- package/server/auth/README.md +17 -17
- package/server/auth/middleware.js +138 -138
- package/server/auth/origin-guard.js +71 -71
- package/server/auth/session.js +108 -108
- package/server/auth/tier.js +113 -113
- package/server/auth/users-store.js +348 -348
- package/server/edit/README.md +29 -29
- package/server/engine/README.md +113 -113
- package/server/engine/agent/agent-shared.js +550 -550
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -4
- package/server/engine/agent/auto-mode-rules.js +78 -78
- package/server/engine/agent/context.js +418 -418
- package/server/engine/agent/events.js +464 -464
- package/server/engine/agent/hooks/canvas-validate.js +199 -199
- package/server/engine/agent/hooks/failure.js +120 -120
- package/server/engine/agent/hooks/file-events.js +75 -75
- package/server/engine/agent/hooks/lifecycle.js +233 -233
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -80
- package/server/engine/agent/hooks/post-guidance.js +206 -206
- package/server/engine/agent/hooks/post-subagent-report.js +104 -104
- package/server/engine/agent/hooks/post-trim.js +58 -58
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -34
- package/server/engine/agent/hooks/pre-defaults.js +84 -84
- package/server/engine/agent/hooks/pre-injectors.js +277 -277
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -78
- package/server/engine/agent/hooks/pre-starter-files.js +61 -61
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -78
- package/server/engine/agent/hooks/site-validate.js +94 -94
- package/server/engine/agent/hooks/tool-prompts.js +31 -31
- package/server/engine/agent/hooks/turn-state-memory.js +56 -56
- package/server/engine/agent/hooks/user-prompt-submit.js +215 -215
- package/server/engine/agent/hooks.js +354 -354
- package/server/engine/agent/init-contract.js +69 -69
- package/server/engine/agent/isolation.js +159 -159
- package/server/engine/agent/model-table.js +292 -292
- package/server/engine/agent/plugin-loader.js +266 -266
- package/server/engine/agent/prompts/nodesign-prelude.md +336 -336
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -71
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -98
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -583
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -483
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -100
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -270
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -70
- package/server/engine/agent/prompts/tools/site-reference.md +106 -106
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -125
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -48
- package/server/engine/agent/session-loop.js +1024 -1024
- package/server/engine/agent/session-model.js +164 -164
- package/server/engine/agent/skill.js +204 -204
- package/server/engine/agent/task-events.js +78 -78
- package/server/engine/agents/ds-extractor.md +132 -132
- package/server/engine/agents/explorer.md +190 -190
- package/server/engine/agents/index.js +230 -230
- package/server/engine/agents/schemas/design-system.json +156 -156
- package/server/engine/agents/schemas/tweak-schema.json +113 -113
- package/server/engine/agents/tweak-proposer.md +127 -127
- package/server/engine/agents/vision-checker.md +254 -254
- package/server/engine/browse/capture.js +422 -422
- package/server/engine/browse/card.js +137 -137
- package/server/engine/browse/handover.js +59 -59
- package/server/engine/browse/page-digest.js +119 -119
- package/server/engine/browse/refs.js +196 -196
- package/server/engine/browse/registry.js +307 -307
- package/server/engine/browse/screencast.js +170 -170
- package/server/engine/browse/state.js +121 -121
- package/server/engine/chatai/chat-log.js +79 -79
- package/server/engine/chatai/index.js +193 -193
- package/server/engine/chatai/openai-compat.js +152 -152
- package/server/engine/chatai/orchestrate.js +309 -309
- package/server/engine/chatai/perform.js +40 -40
- package/server/engine/chatai/summarize.js +79 -79
- package/server/engine/mcp/capability-gate.js +52 -52
- package/server/engine/mcp/index.js +339 -339
- package/server/engine/mcp/param-sanitizer.js +142 -142
- package/server/engine/mcp/tools/arrange-on-board.js +124 -124
- package/server/engine/mcp/tools/artifact-session.js +278 -278
- package/server/engine/mcp/tools/browse-computer.js +421 -421
- package/server/engine/mcp/tools/browse-find-batch.js +183 -183
- package/server/engine/mcp/tools/browse-screenshot.js +157 -157
- package/server/engine/mcp/tools/browse.js +480 -480
- package/server/engine/mcp/tools/build-docx.js +101 -101
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -99
- package/server/engine/mcp/tools/create-on-board.js +155 -155
- package/server/engine/mcp/tools/crystallize-skill.js +168 -168
- package/server/engine/mcp/tools/explain-style.js +237 -237
- package/server/engine/mcp/tools/export-handoff.js +133 -133
- package/server/engine/mcp/tools/expose-tweaks.js +149 -149
- package/server/engine/mcp/tools/generate-image.js +842 -842
- package/server/engine/mcp/tools/get-computed-styles.js +164 -164
- package/server/engine/mcp/tools/get-pending-changes.js +205 -205
- package/server/engine/mcp/tools/h3box-ssh.js +84 -84
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
- package/server/engine/mcp/tools/highlight.js +68 -68
- package/server/engine/mcp/tools/list-pages.js +222 -222
- package/server/engine/mcp/tools/lookup-tags.js +59 -59
- package/server/engine/mcp/tools/navigate-to-page.js +60 -60
- package/server/engine/mcp/tools/organize-board.js +64 -64
- package/server/engine/mcp/tools/paint-still.js +434 -434
- package/server/engine/mcp/tools/pin-to-board.js +130 -130
- package/server/engine/mcp/tools/preview-deck.js +130 -130
- package/server/engine/mcp/tools/profile-scroll.js +305 -305
- package/server/engine/mcp/tools/publish-site.js +102 -102
- package/server/engine/mcp/tools/query-elements.js +195 -195
- package/server/engine/mcp/tools/read-board.js +103 -103
- package/server/engine/mcp/tools/read-document.js +93 -93
- package/server/engine/mcp/tools/read-page.js +264 -264
- package/server/engine/mcp/tools/read-tavern-json.js +143 -143
- package/server/engine/mcp/tools/record-decision.js +140 -140
- package/server/engine/mcp/tools/relate-on-board.js +110 -110
- package/server/engine/mcp/tools/report-issue.js +115 -115
- package/server/engine/mcp/tools/roll-film.js +266 -266
- package/server/engine/mcp/tools/screenshot-docx.js +138 -138
- package/server/engine/mcp/tools/screenshot-url.js +195 -195
- package/server/engine/mcp/tools/screenshot.js +583 -583
- package/server/engine/mcp/tools/tier-gate.js +96 -96
- package/server/engine/mcp/tools/trace-motion.js +215 -215
- package/server/engine/mcp/tools/web-search.js +548 -548
- package/server/engine/motion/inventory.js +349 -349
- package/server/engine/perception/session.js +235 -235
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -192
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -162
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -228
- package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -78
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -323
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
- package/server/engine/runs/active-runs.js +698 -698
- package/server/engine/runs/live-turn.js +252 -252
- package/server/engine/runs/store.js +364 -364
- package/server/engine/runs/turn-relay.js +217 -217
- package/server/engine/runtime/workspace.js +164 -164
- package/server/lib/artifact-file-path.js +80 -80
- package/server/lib/artifact-target.js +284 -284
- package/server/lib/asset-refs.js +239 -239
- package/server/lib/async-queue.js +104 -104
- package/server/lib/auto-relations.js +130 -130
- package/server/lib/binding-types.js +60 -60
- package/server/lib/board-kind-sizes.js +62 -62
- package/server/lib/board-relations.js +133 -133
- package/server/lib/browse-proxy.js +180 -180
- package/server/lib/canvas-id.js +36 -36
- package/server/lib/cover.js +227 -227
- package/server/lib/danbooru-tags.js +291 -291
- package/server/lib/doc-extract.js +156 -156
- package/server/lib/docx/build-from-source.js +260 -260
- package/server/lib/docx/build.js +490 -490
- package/server/lib/docx/dump-styles.js +287 -287
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
- package/server/lib/docx/merge-runs.js +129 -129
- package/server/lib/docx/numbering.js +218 -218
- package/server/lib/docx/order.js +152 -152
- package/server/lib/docx/rawzip.js +172 -172
- package/server/lib/docx/render.js +92 -92
- package/server/lib/docx/text-lint.js +225 -225
- package/server/lib/docx/tokens.js +335 -335
- package/server/lib/docx/units.js +29 -29
- package/server/lib/docx/xml.js +291 -291
- package/server/lib/docx-pages.js +187 -187
- package/server/lib/export-collect.js +289 -289
- package/server/lib/export-package.js +210 -210
- package/server/lib/html-srcset.js +145 -145
- package/server/lib/image-variant.js +460 -460
- package/server/lib/ingress/forward-openai-chat.js +305 -305
- package/server/lib/ingress/openai-chat.js +491 -491
- package/server/lib/ingress/session-notice.js +66 -66
- package/server/lib/ingress/session-routes.js +68 -68
- package/server/lib/ingress/slot-probe.js +130 -130
- package/server/lib/ingress/upstream-billing.js +52 -52
- package/server/lib/ingress/upstream-fail-streak.js +72 -72
- package/server/lib/ingress/upstream-truncation.js +48 -48
- package/server/lib/issues-store.js +182 -182
- package/server/lib/kinds/deck.js +72 -72
- package/server/lib/kinds/docx.js +283 -283
- package/server/lib/kinds/file-kinds.js +113 -113
- package/server/lib/kinds/index.js +202 -202
- package/server/lib/kinds/site.js +161 -161
- package/server/lib/model-ingress.js +543 -543
- package/server/lib/moderation.js +255 -255
- package/server/lib/notice-store.js +103 -103
- package/server/lib/plugin-install.js +104 -104
- package/server/lib/plugin-validator.js +721 -721
- package/server/lib/publish-store.js +115 -115
- package/server/lib/quick-summary.js +52 -52
- package/server/lib/quota.js +270 -270
- package/server/lib/rate-window.js +29 -29
- package/server/lib/reference-assets.js +92 -92
- package/server/lib/region-shot.js +135 -135
- package/server/lib/safe-path.js +73 -73
- package/server/lib/sdk-session.js +33 -33
- package/server/lib/showcase-store.js +92 -92
- package/server/lib/site-publish.js +465 -465
- package/server/lib/ssrf-guard.js +432 -432
- package/server/lib/task-scan.js +158 -158
- package/server/lib/tavern-json.js +154 -154
- package/server/lib/video-variant.js +237 -237
- package/server/lib/workspace-path.js +33 -33
- package/server/ops/fix-sdk-musl.mjs +40 -40
- package/server/ops/install-macos-fonts.sh +114 -114
- package/server/ops/macos-fonts.conf +336 -336
- package/server/ops/sandbox-shim/bwrap +57 -57
- package/server/projects/assets-summary.js +119 -119
- package/server/projects/auto-name.js +53 -53
- package/server/projects/board-store.js +640 -640
- package/server/projects/move-entry.js +103 -103
- package/server/projects/store.js +270 -270
- package/server/projects/ui-config.js +54 -54
- package/server/projects/workspace-templates.js +53 -53
- package/server/projects/workspace.js +1025 -1025
- package/server/runtime/local-config.js +177 -177
- package/server/runtime/profile.js +72 -72
- package/server/services/rembg-service.py +334 -334
- package/server/shared/README.md +25 -25
- package/server/shared/deck.js +54 -54
- package/server/shared/time.js +49 -49
- package/server/style-pipeline/README.md +53 -53
- package/server/ws/broker.js +36 -36
- package/server/ws/browse-channel.js +177 -177
- package/server/ws/index.js +386 -386
- package/web/README.md +89 -89
- package/web/dist/assets/BrowserWindow-BYCgizHw.js +11 -0
- package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-pNydEM4L.js} +1 -1
- package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-PPgzh3Gq.js} +1 -1
- package/web/dist/assets/{SiteWindow-D6eS-2gM.js → SiteWindow-DM0LQPCO.js} +1 -1
- package/web/dist/assets/{index-Cbl3ATvp.js → index-BHTBJlpY.js} +32 -32
- package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-C0I13CbD.js} +1 -1
- package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-B1UQ8Hld.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-8lrgqmfv.js} +1 -1
- package/web/dist/index.html +17 -17
- package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +0 -11
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* helpers/motion-scroll.js — 录制期的真滚轮驱动 + 元素探针报告(2026-08-21)
|
|
3
|
-
*
|
|
4
|
-
* 跟 motion-lab.js 配套、对页面无知:浏览通道(参考站)和感知通道(自己的产物)共用。
|
|
5
|
-
*
|
|
6
|
-
* - wheelScroll:在 recordMotion 的 `during` 里跑 —— 按 16ms 一 tick 派发真滚轮事件,
|
|
7
|
-
* 跟 profile_scroll 同一条输入路径(滚动劫持那族只认这个,window.scrollTo 对它们无效)。
|
|
8
|
-
* - elementMotionReport:motion-lab 元素探针的逐帧样本 → "谁在动、怎么动"的人话。
|
|
9
|
-
* 样本是文档坐标,所以滚动本身不算动;算的是入场(位移+透明度)、视差(位移跟
|
|
10
|
-
* 滚动量不成比例)、缩放、固定元素随视口走。
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* 真滚轮滚 px 像素,摊在 durationMs 里。返回一句给 caption 的话(派发了多少、页面真滚了多少)。
|
|
15
|
-
* @param {import('playwright').Page} page
|
|
16
|
-
*/
|
|
17
|
-
export async function wheelScroll(page, { px, durationMs, at = null }) {
|
|
18
|
-
const vp = page.viewportSize() || { width: 1280, height: 800 };
|
|
19
|
-
const [cx, cy] = at || [Math.floor(vp.width / 2), Math.floor(vp.height / 2)];
|
|
20
|
-
await page.mouse.move(cx, cy);
|
|
21
|
-
const y0 = await page.evaluate(() => window.scrollY).catch(() => 0);
|
|
22
|
-
// 按**时间预算**派发,不按固定 tick 数:每次 mouse.wheel 在这台 1 vCPU 机器上是一次
|
|
23
|
-
// 往返(10~30ms 不等),固定 16ms × N 次会拖过录制窗口(真跑逮到:劫持说明没赶上
|
|
24
|
-
// caption)。改成"过了多少时间就该派到多少像素",到点必停。
|
|
25
|
-
const t0 = Date.now();
|
|
26
|
-
let dispatched = 0;
|
|
27
|
-
while (true) {
|
|
28
|
-
const elapsed = Date.now() - t0;
|
|
29
|
-
const due = Math.min(px, px * (elapsed / durationMs));
|
|
30
|
-
const delta = due - dispatched;
|
|
31
|
-
if (Math.abs(delta) >= 1) { await page.mouse.wheel(0, delta); dispatched += delta; }
|
|
32
|
-
if (elapsed >= durationMs) break;
|
|
33
|
-
await page.waitForTimeout(16);
|
|
34
|
-
}
|
|
35
|
-
if (Math.abs(px - dispatched) >= 1) { await page.mouse.wheel(0, px - dispatched); dispatched = px; }
|
|
36
|
-
await page.waitForTimeout(80);
|
|
37
|
-
const y1 = await page.evaluate(() => window.scrollY).catch(() => y0);
|
|
38
|
-
const moved = y1 - y0;
|
|
39
|
-
const note = `wheel-scrolled ${Math.round(px)}px over ${durationMs}ms → window moved ${Math.round(moved)}px`;
|
|
40
|
-
if (px > 200 && Math.abs(moved) < Math.abs(px) * 0.5) {
|
|
41
|
-
return `${note} — ⚠ the page moved far less than dispatched: scroll hijacking (Lenis/Locomotive-style smooth scroll) or a scroll container that is not the window`;
|
|
42
|
-
}
|
|
43
|
-
return note;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** 一个元素的样本 → 统计 */
|
|
47
|
-
function summarize(e) {
|
|
48
|
-
const s = e.samples;
|
|
49
|
-
if (s.length < 3) return null;
|
|
50
|
-
let minX = Infinity; let maxX = -Infinity; let minY = Infinity; let maxY = -Infinity;
|
|
51
|
-
let minO = Infinity; let maxO = -Infinity; let minS = Infinity; let maxS = -Infinity;
|
|
52
|
-
for (const [, x, y, o, sc] of s) {
|
|
53
|
-
if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y;
|
|
54
|
-
if (o < minO) minO = o; if (o > maxO) maxO = o; if (sc < minS) minS = sc; if (sc > maxS) maxS = sc;
|
|
55
|
-
}
|
|
56
|
-
const dx = maxX - minX; const dy = maxY - minY;
|
|
57
|
-
const travel = Math.hypot(dx, dy);
|
|
58
|
-
const fade = maxO - minO;
|
|
59
|
-
const scale = maxS - minS;
|
|
60
|
-
// 首末变化时刻:第一个/最后一个和起点不同的样本
|
|
61
|
-
const [t00, x0, y0, o0, s0] = s[0];
|
|
62
|
-
const diff = (r) => Math.abs(r[1] - x0) > 1 || Math.abs(r[2] - y0) > 1 || Math.abs(r[3] - o0) > 0.02 || Math.abs(r[4] - s0) > 0.01;
|
|
63
|
-
let start = null; let end = null;
|
|
64
|
-
for (const r of s) { if (diff(r)) { if (start == null) start = r[0]; end = r[0]; } }
|
|
65
|
-
return { sel: e.sel, fixed: !!e.fixed, travel, dx, dy, fade, scale, from: { x: x0, y: y0, o: o0, s: s0 }, to: { x: s[s.length - 1][1], y: s[s.length - 1][2], o: s[s.length - 1][3], s: s[s.length - 1][4] }, startMs: start, endMs: end, t0: t00, n: s.length };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 元素探针样本 → 报告。`scrolledPx`(录制期 window 真滚了多少)用来把"随视口走的固定
|
|
70
|
-
* 元素"和"视差"从"真入场"里分开:文档坐标下固定元素的位移 ≈ 滚动量,视差 ≈ 滚动量的
|
|
71
|
-
* 一个比例,入场动画的位移跟滚动量无关。
|
|
72
|
-
*/
|
|
73
|
-
export function elementMotionReport(elems, { scrolledPx = 0 } = {}) {
|
|
74
|
-
const rows = (elems || []).map(summarize).filter(Boolean);
|
|
75
|
-
const moving = rows.filter(r => r.travel > 3 || r.fade > 0.05 || r.scale > 0.02);
|
|
76
|
-
const classify = (r) => {
|
|
77
|
-
const tags = [];
|
|
78
|
-
// fixed/sticky 层(含其子元素):文档坐标天然随视口走,报成"固定层"而不是视差/位移
|
|
79
|
-
if (r.fixed && r.travel > 3) tags.push(`fixed/sticky 层(随视口走,dy ${r.dy}${scrolledPx ? `,滚动量 ${Math.round(scrolledPx)}` : ''})`);
|
|
80
|
-
// 视差:位移是滚动量的一个明显比例(≥15%)且只在竖向、不带淡入 —— 入场那种 40~120px
|
|
81
|
-
// 的平移在上千像素的滚动里占比很小,归"位移"不归视差
|
|
82
|
-
else if (scrolledPx && Math.abs(r.dy) >= Math.abs(scrolledPx) * 0.15 && Math.abs(r.dy) < Math.abs(scrolledPx) * 0.95 && Math.abs(r.dx) < 8 && r.fade < 0.05) tags.push(`视差(位移≈滚动量的 ${(Math.abs(r.dy) / Math.abs(scrolledPx) * 100).toFixed(0)}%)`);
|
|
83
|
-
else if (r.travel > 3) tags.push(`位移 ${Math.round(r.travel)}px(dx ${r.dx}, dy ${r.dy})`);
|
|
84
|
-
if (r.fade > 0.05) tags.push(`透明度 ${r.from.o}→${r.to.o}`);
|
|
85
|
-
if (r.scale > 0.02) tags.push(`缩放 ${r.from.s}→${r.to.s}`);
|
|
86
|
-
return tags;
|
|
87
|
-
};
|
|
88
|
-
const score = (r) => r.travel + r.fade * 200 + r.scale * 200;
|
|
89
|
-
// 固定层排最后:它们"动"是因为视口动了,真正有信息量的是前面那些
|
|
90
|
-
const items = moving.map(r => ({ ...r, tags: classify(r) }))
|
|
91
|
-
.sort((a, b) => (a.fixed === b.fixed ? score(b) - score(a) : (a.fixed ? 1 : -1)));
|
|
92
|
-
return { total: rows.length, moving: items.length, items };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** 报告 → 给 agent 的行。固定层的子元素合并成一行(真跑时一个页头 15 个链接刷了 15 行)。 */
|
|
96
|
-
export function elementMotionLines(rep, { max = 10 } = {}) {
|
|
97
|
-
if (!rep || !rep.total) return ['element probe: no elements sampled'];
|
|
98
|
-
if (!rep.moving) return [`element probe: ${rep.total} elements watched, none moved/faded/scaled during the recording`];
|
|
99
|
-
const free = rep.items.filter(r => !r.fixed);
|
|
100
|
-
const fixed = rep.items.filter(r => r.fixed);
|
|
101
|
-
const lines = [`element probe: ${rep.moving} of ${rep.total} watched elements moved during the recording:`];
|
|
102
|
-
for (const r of free.slice(0, max)) {
|
|
103
|
-
const when = r.startMs != null ? `${Math.max(0, Math.round(r.startMs))}→${Math.round(r.endMs)}ms` : '';
|
|
104
|
-
lines.push(` ${r.sel} ${r.tags.join(' · ')}${when ? ` [${when}]` : ''}`);
|
|
105
|
-
}
|
|
106
|
-
if (free.length > max) lines.push(` (+${free.length - max} more)`);
|
|
107
|
-
if (fixed.length) {
|
|
108
|
-
const names = [...new Set(fixed.map(r => r.sel))].slice(0, 4).join(', ');
|
|
109
|
-
lines.push(` fixed/sticky 层 ×${fixed.length}(${names}${fixed.length > 4 ? ' …' : ''})随视口走 —— 页头/浮层那类,不是页面内容在动`);
|
|
110
|
-
}
|
|
111
|
-
return lines;
|
|
112
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* helpers/motion-scroll.js — 录制期的真滚轮驱动 + 元素探针报告(2026-08-21)
|
|
3
|
+
*
|
|
4
|
+
* 跟 motion-lab.js 配套、对页面无知:浏览通道(参考站)和感知通道(自己的产物)共用。
|
|
5
|
+
*
|
|
6
|
+
* - wheelScroll:在 recordMotion 的 `during` 里跑 —— 按 16ms 一 tick 派发真滚轮事件,
|
|
7
|
+
* 跟 profile_scroll 同一条输入路径(滚动劫持那族只认这个,window.scrollTo 对它们无效)。
|
|
8
|
+
* - elementMotionReport:motion-lab 元素探针的逐帧样本 → "谁在动、怎么动"的人话。
|
|
9
|
+
* 样本是文档坐标,所以滚动本身不算动;算的是入场(位移+透明度)、视差(位移跟
|
|
10
|
+
* 滚动量不成比例)、缩放、固定元素随视口走。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 真滚轮滚 px 像素,摊在 durationMs 里。返回一句给 caption 的话(派发了多少、页面真滚了多少)。
|
|
15
|
+
* @param {import('playwright').Page} page
|
|
16
|
+
*/
|
|
17
|
+
export async function wheelScroll(page, { px, durationMs, at = null }) {
|
|
18
|
+
const vp = page.viewportSize() || { width: 1280, height: 800 };
|
|
19
|
+
const [cx, cy] = at || [Math.floor(vp.width / 2), Math.floor(vp.height / 2)];
|
|
20
|
+
await page.mouse.move(cx, cy);
|
|
21
|
+
const y0 = await page.evaluate(() => window.scrollY).catch(() => 0);
|
|
22
|
+
// 按**时间预算**派发,不按固定 tick 数:每次 mouse.wheel 在这台 1 vCPU 机器上是一次
|
|
23
|
+
// 往返(10~30ms 不等),固定 16ms × N 次会拖过录制窗口(真跑逮到:劫持说明没赶上
|
|
24
|
+
// caption)。改成"过了多少时间就该派到多少像素",到点必停。
|
|
25
|
+
const t0 = Date.now();
|
|
26
|
+
let dispatched = 0;
|
|
27
|
+
while (true) {
|
|
28
|
+
const elapsed = Date.now() - t0;
|
|
29
|
+
const due = Math.min(px, px * (elapsed / durationMs));
|
|
30
|
+
const delta = due - dispatched;
|
|
31
|
+
if (Math.abs(delta) >= 1) { await page.mouse.wheel(0, delta); dispatched += delta; }
|
|
32
|
+
if (elapsed >= durationMs) break;
|
|
33
|
+
await page.waitForTimeout(16);
|
|
34
|
+
}
|
|
35
|
+
if (Math.abs(px - dispatched) >= 1) { await page.mouse.wheel(0, px - dispatched); dispatched = px; }
|
|
36
|
+
await page.waitForTimeout(80);
|
|
37
|
+
const y1 = await page.evaluate(() => window.scrollY).catch(() => y0);
|
|
38
|
+
const moved = y1 - y0;
|
|
39
|
+
const note = `wheel-scrolled ${Math.round(px)}px over ${durationMs}ms → window moved ${Math.round(moved)}px`;
|
|
40
|
+
if (px > 200 && Math.abs(moved) < Math.abs(px) * 0.5) {
|
|
41
|
+
return `${note} — ⚠ the page moved far less than dispatched: scroll hijacking (Lenis/Locomotive-style smooth scroll) or a scroll container that is not the window`;
|
|
42
|
+
}
|
|
43
|
+
return note;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 一个元素的样本 → 统计 */
|
|
47
|
+
function summarize(e) {
|
|
48
|
+
const s = e.samples;
|
|
49
|
+
if (s.length < 3) return null;
|
|
50
|
+
let minX = Infinity; let maxX = -Infinity; let minY = Infinity; let maxY = -Infinity;
|
|
51
|
+
let minO = Infinity; let maxO = -Infinity; let minS = Infinity; let maxS = -Infinity;
|
|
52
|
+
for (const [, x, y, o, sc] of s) {
|
|
53
|
+
if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y;
|
|
54
|
+
if (o < minO) minO = o; if (o > maxO) maxO = o; if (sc < minS) minS = sc; if (sc > maxS) maxS = sc;
|
|
55
|
+
}
|
|
56
|
+
const dx = maxX - minX; const dy = maxY - minY;
|
|
57
|
+
const travel = Math.hypot(dx, dy);
|
|
58
|
+
const fade = maxO - minO;
|
|
59
|
+
const scale = maxS - minS;
|
|
60
|
+
// 首末变化时刻:第一个/最后一个和起点不同的样本
|
|
61
|
+
const [t00, x0, y0, o0, s0] = s[0];
|
|
62
|
+
const diff = (r) => Math.abs(r[1] - x0) > 1 || Math.abs(r[2] - y0) > 1 || Math.abs(r[3] - o0) > 0.02 || Math.abs(r[4] - s0) > 0.01;
|
|
63
|
+
let start = null; let end = null;
|
|
64
|
+
for (const r of s) { if (diff(r)) { if (start == null) start = r[0]; end = r[0]; } }
|
|
65
|
+
return { sel: e.sel, fixed: !!e.fixed, travel, dx, dy, fade, scale, from: { x: x0, y: y0, o: o0, s: s0 }, to: { x: s[s.length - 1][1], y: s[s.length - 1][2], o: s[s.length - 1][3], s: s[s.length - 1][4] }, startMs: start, endMs: end, t0: t00, n: s.length };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 元素探针样本 → 报告。`scrolledPx`(录制期 window 真滚了多少)用来把"随视口走的固定
|
|
70
|
+
* 元素"和"视差"从"真入场"里分开:文档坐标下固定元素的位移 ≈ 滚动量,视差 ≈ 滚动量的
|
|
71
|
+
* 一个比例,入场动画的位移跟滚动量无关。
|
|
72
|
+
*/
|
|
73
|
+
export function elementMotionReport(elems, { scrolledPx = 0 } = {}) {
|
|
74
|
+
const rows = (elems || []).map(summarize).filter(Boolean);
|
|
75
|
+
const moving = rows.filter(r => r.travel > 3 || r.fade > 0.05 || r.scale > 0.02);
|
|
76
|
+
const classify = (r) => {
|
|
77
|
+
const tags = [];
|
|
78
|
+
// fixed/sticky 层(含其子元素):文档坐标天然随视口走,报成"固定层"而不是视差/位移
|
|
79
|
+
if (r.fixed && r.travel > 3) tags.push(`fixed/sticky 层(随视口走,dy ${r.dy}${scrolledPx ? `,滚动量 ${Math.round(scrolledPx)}` : ''})`);
|
|
80
|
+
// 视差:位移是滚动量的一个明显比例(≥15%)且只在竖向、不带淡入 —— 入场那种 40~120px
|
|
81
|
+
// 的平移在上千像素的滚动里占比很小,归"位移"不归视差
|
|
82
|
+
else if (scrolledPx && Math.abs(r.dy) >= Math.abs(scrolledPx) * 0.15 && Math.abs(r.dy) < Math.abs(scrolledPx) * 0.95 && Math.abs(r.dx) < 8 && r.fade < 0.05) tags.push(`视差(位移≈滚动量的 ${(Math.abs(r.dy) / Math.abs(scrolledPx) * 100).toFixed(0)}%)`);
|
|
83
|
+
else if (r.travel > 3) tags.push(`位移 ${Math.round(r.travel)}px(dx ${r.dx}, dy ${r.dy})`);
|
|
84
|
+
if (r.fade > 0.05) tags.push(`透明度 ${r.from.o}→${r.to.o}`);
|
|
85
|
+
if (r.scale > 0.02) tags.push(`缩放 ${r.from.s}→${r.to.s}`);
|
|
86
|
+
return tags;
|
|
87
|
+
};
|
|
88
|
+
const score = (r) => r.travel + r.fade * 200 + r.scale * 200;
|
|
89
|
+
// 固定层排最后:它们"动"是因为视口动了,真正有信息量的是前面那些
|
|
90
|
+
const items = moving.map(r => ({ ...r, tags: classify(r) }))
|
|
91
|
+
.sort((a, b) => (a.fixed === b.fixed ? score(b) - score(a) : (a.fixed ? 1 : -1)));
|
|
92
|
+
return { total: rows.length, moving: items.length, items };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 报告 → 给 agent 的行。固定层的子元素合并成一行(真跑时一个页头 15 个链接刷了 15 行)。 */
|
|
96
|
+
export function elementMotionLines(rep, { max = 10 } = {}) {
|
|
97
|
+
if (!rep || !rep.total) return ['element probe: no elements sampled'];
|
|
98
|
+
if (!rep.moving) return [`element probe: ${rep.total} elements watched, none moved/faded/scaled during the recording`];
|
|
99
|
+
const free = rep.items.filter(r => !r.fixed);
|
|
100
|
+
const fixed = rep.items.filter(r => r.fixed);
|
|
101
|
+
const lines = [`element probe: ${rep.moving} of ${rep.total} watched elements moved during the recording:`];
|
|
102
|
+
for (const r of free.slice(0, max)) {
|
|
103
|
+
const when = r.startMs != null ? `${Math.max(0, Math.round(r.startMs))}→${Math.round(r.endMs)}ms` : '';
|
|
104
|
+
lines.push(` ${r.sel} ${r.tags.join(' · ')}${when ? ` [${when}]` : ''}`);
|
|
105
|
+
}
|
|
106
|
+
if (free.length > max) lines.push(` (+${free.length - max} more)`);
|
|
107
|
+
if (fixed.length) {
|
|
108
|
+
const names = [...new Set(fixed.map(r => r.sel))].slice(0, 4).join(', ');
|
|
109
|
+
lines.push(` fixed/sticky 层 ×${fixed.length}(${names}${fixed.length > 4 ? ' …' : ''})随视口走 —— 页头/浮层那类,不是页面内容在动`);
|
|
110
|
+
}
|
|
111
|
+
return lines;
|
|
112
|
+
}
|