万象-网页开发与验收

SKILL开发
开发 未标注权限 许可证不明确
安装这个技能

从写页面到发布上线一条龙:界面与动效、交付前验收、登录态定时巡检、资料检索取证、正式发布。

一句话选型结论

按资料字段整理

万象-网页开发与验收是开发类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类开发 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_de2353f3/omni-web
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

开发
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_de2353f3仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验未标注上游最后核验时间 · 数据更新 1791671829
正文体量100.0 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
写代码与改 bug

按需求补代码、定位并修掉报错。

读仓库与生成改动

让模型读懂项目结构,再给出可落地的改动。

跑测试与排查故障

按现象排查问题,补测试用例并复跑。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在开发场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
万象-网页开发与验收当前 未标注权限 未标注 未标注 未标注 有
技能审查员 浏览器控制 ClawHub 市场 877.9 万 OpenClaw 有
Vercel React 最佳实践 浏览器控制 GitHub 仓库 849.1 万 Codex、Claude、Cursor 有
GitHub 代码协作 联网访问 ClawHub 市场 763.3 万 OpenClaw 有
网页设计指南 联网访问 GitHub 仓库 668.6 万 Codex、Claude、Cursor 有
本体论 只读 ClawHub 市场 663.7 万 OpenClaw 有

SKILL.md 全文

100.0 KB
模型实际读到的说明书原文,来自技能仓库

万象-网页开发与验收

一句话流程:
> 取(D 篇:先查真伪再取内容,拿到可追溯的材料)→ 造(A 篇)→ 验(B 篇:结构 → 渲染 → 交互 → 截图 → 会话 → 填表,提交留给人工)→ 守(C 篇:登录态定时巡检)→ 发(E 篇:发布为应用,拿到可分享链接)
>
> D 篇可与任意一篇并列单独使用;A/B/C 需要外部资料时,先走 D 篇。要对外上线时走 E 篇。

总路由:先判任务落在哪一篇

任务 走哪篇
从零造页面 / 落地页 / 大屏,生成 AI 素材、动效系统、转化文案、生成艺术 A 篇 · 网页开发
页面已产出,要交付前自查 / 验证能渲染能点击 / 响应式与整页截图 / SPA 会话修复 / 批量填表 B 篇 · 网页验收
登录态网页的定时自动化(签到 / 领取 / 巡检 / 点按钮) C 篇 · 登录巡检
要从网上找一份文档/手册/规范、判断某份资料或说法真伪、抓取网页正文并留痕 D 篇 · 检索取证
页面已验收、要一个可分享的链接,或要下线已发布的应用 E 篇 · 发布上线

五篇边界

  1. D 管「材料从哪来、可不可信」,A 管「怎么造出来」,B 管「造出来能不能用、敢不敢交付」,C 管「交付后天天自动跑」,E 管「怎么让别人看到」。
  2. B 的三层验证与 C 的 agent-browser 巡检共用本机 Edge 内核,但 B 用 CDP 做一次性断言、C 用持久化 profile 做长期定时任务——profile 约定不同,不要混用。
  3. B 铁律 3「提交审核默认不点」在 C 同样适用:自动化只替代规则里「手动」的动作,是否开启由用户决定。
  4. A 产出物交给 B 验收后才算交付;B 验收发现的视觉问题回 A 修,行为问题按 B 各节修法处理。
  5. D 与 A 的分界在「料」与「工」:D 只负责把外部材料取回来并证明它可信(留痕、定级、交叉验证),不负责写页面、不负责蒸馏成技能;蒸馏交给 万象-知识蒸馏。D 抓正文用的浏览器工具与 B/C 同源,但 D 不驱动浏览器做交互,只取文本。

  6. E 与 A/B 的分界在「我的机器」与「别人的浏览器」:A/B 都发生在本地,E 一旦执行就是对外可见的动作 —— 故 E 篇的每条纪律都围绕「要不要发、发得对不对」。


A 篇 · 网页开发(造)

Build complete, production-ready frontend pages by orchestrating 5 specialized capabilities: design engineering, motion systems, AI-generated assets, persuasive copy, and generative art.

Invocation

/frontend-dev <request>    # 旧名仍等效;本技能现名 万象-网页开发与验收

The user provides their request as natural language (e.g. "build a landing page for a music streaming app").

Skill Structure

omni-web/
├── SKILL.md                      # Core skill (this file)
├── scripts/                      # Asset generation scripts
│   ├── minimax_tts.py            # Text-to-speech
│   ├── minimax_music.py          # Music generation
│   ├── minimax_video.py          # Video generation (async)
│   └── minimax_image.py          # Image generation
├── references/                   # Detailed guides (read as needed)
│   ├── minimax-cli-reference.md  # CLI flags quick reference
│   ├── asset-prompt-guide.md     # Asset prompt engineering rules
│   ├── minimax-tts-guide.md      # TTS usage & voices
│   ├── minimax-music-guide.md    # Music prompts & lyrics format
│   ├── minimax-video-guide.md    # Camera commands & models
│   ├── minimax-image-guide.md    # Ratios & batch generation
│   ├── minimax-voice-catalog.md  # All voice IDs
│   ├── motion-recipes.md         # Animation code snippets
│   ├── env-setup.md              # Environment setup
│   └── troubleshooting.md        # Common issues
├── templates/                    # Visual art templates
│   ├── viewer.html               # p5.js interactive art base
│   └── generator_template.js     # p5.js code reference
└── canvas-fonts/                 # Static art fonts (TTF + licenses)

Project Structure

Assets (Universal)

All frameworks use the same asset organization:

assets/
├── images/
│   ├── hero-landing-1710xxx.webp
│   ├── icon-feature-01.webp
│   └── bg-pattern.svg
├── videos/
│   ├── hero-bg-1710xxx.mp4
│   └── demo-preview.mp4
└── audio/
    ├── bgm-ambient-1710xxx.mp3
    └── tts-intro-1710xxx.mp3

Asset naming: {type}-{descriptor}-{timestamp}.{ext}

By Framework

Framework Asset Location Component Location
Pure HTML ./assets/ N/A (inline or ./js/)
React/Next.js public/assets/ src/components/
Vue/Nuxt public/assets/ src/components/
Svelte/SvelteKit static/assets/ src/lib/components/
Astro public/assets/ src/components/

Pure HTML

project/
├── index.html
├── assets/
│   ├── images/
│   ├── videos/
│   └── audio/
├── css/
│   └── styles.css
└── js/
    └── main.js           # Animations (GSAP/vanilla)

React / Next.js

project/
├── public/assets/        # Static assets
├── src/
│   ├── components/
│   │   ├── ui/           # Button, Card, Input
│   │   ├── sections/     # Hero, Features, CTA
│   │   └── motion/       # RevealSection, StaggerGrid
│   ├── lib/
│   ├── styles/
│   └── app/              # Pages
└── package.json

Vue / Nuxt

project/
├── public/assets/
├── src/                  # or root for Nuxt
│   ├── components/
│   │   ├── ui/
│   │   ├── sections/
│   │   └── motion/
│   ├── composables/      # Shared logic
│   ├── pages/
│   └── assets/           # Processed assets (optional)
└── package.json

Astro

project/
├── public/assets/
├── src/
│   ├── components/       # .astro, .tsx, .vue, .svelte
│   ├── layouts/
│   ├── pages/
│   └── styles/
└── package.json

Component naming: PascalCase (HeroSection.tsx, HeroSection.vue, HeroSection.astro)


Compliance

All rules in this skill are mandatory. Violating any rule is a blocking error — fix before proceeding or delivering.


Workflow

Phase 1: Design Architecture

  1. Analyze the request — determine page type and context
  2. Set design dials based on page type
  3. Plan layout sections and identify asset needs

Phase 2: Motion Architecture

  1. Select animation tools per section (see Tool Selection Matrix)
  2. Plan motion sequences following performance guardrails

Phase 3: Asset Generation

Generate all image/video/audio assets using scripts/. NEVER use placeholder URLs (unsplash, picsum, placeholder.com, via.placeholder, placehold.co, etc.) or external URLs.

  1. Parse asset requirements (type, style, spec, usage)
  2. Craft optimized prompts, show to user, confirm before generating
  3. Execute via scripts, save to project — do NOT proceed to Phase 5 until all assets are saved locally

Phase 4: Copywriting & Content

Follow copywriting frameworks (AIDA, PAS, FAB) to craft all text content. Do NOT use "Lorem ipsum" — write real copy.

Phase 5: Build UI

Scaffold the project and build each section following Design and Motion rules. Integrate generated assets and copy. All <img>, <video>, <source>, and CSS background-image MUST reference local assets from Phase 3.

Phase 6: Quality Gates

Run final checklist (see Quality Gates section).


1. Design Engineering

1.1 Baseline Configuration

Dial Default Range
DESIGN_VARIANCE 8 1=Symmetry, 10=Asymmetric
MOTION_INTENSITY 6 1=Static, 10=Cinematic
VISUAL_DENSITY 4 1=Airy, 10=Packed

Adapt dynamically based on user requests.

1.2 Architecture Conventions

  • DEPENDENCY VERIFICATION: Check package.json before importing any library. Output install command if missing.
  • Framework: React/Next.js. Default to Server Components. Interactive components must be isolated "use client" leaf components.
  • Styling: Tailwind CSS. Check version in package.json — NEVER mix v3/v4 syntax.
  • ANTI-EMOJI POLICY: NEVER use emojis anywhere. Use Phosphor or Radix icons only.
  • Viewport: Use min-h-[100dvh] not h-screen. Use CSS Grid not flex percentage math.
  • Layout: max-w-[1400px] mx-auto or max-w-7xl.

1.3 Design Rules

Rule Directive
Typography Headlines: text-4xl md:text-6xl tracking-tighter. Body: text-base leading-relaxed max-w-[65ch]. NEVER use Inter — use Geist/Outfit/Satoshi. NEVER use Serif on dashboards.
Color Max 1 accent, saturation < 80%. NEVER use AI purple/blue. Stick to one palette.
Layout NEVER use centered heroes when VARIANCE > 4. Force split-screen or asymmetric layouts.
Cards NEVER use generic cards when DENSITY > 7. Use border-t, divide-y, or spacing.
States ALWAYS implement: Loading (skeleton), Empty, Error, Tactile feedback (scale-[0.98]).
Forms Label above input. Error below. gap-2 for input blocks.

1.4 Anti-Slop Techniques

  • Liquid Glass: backdrop-blur + border-white/10 + shadow-[inset_0_1px_0_rgba(255,255,255,0.1)]
  • Magnetic Buttons: Use useMotionValue/useTransform — never useState for continuous animations
  • Perpetual Motion: When INTENSITY > 5, add infinite micro-animations (Pulse, Float, Shimmer)
  • Layout Transitions: Use Framer layout and layoutId props
  • Stagger: Use staggerChildren or CSS animation-delay: calc(var(--index) * 100ms)

1.5 Forbidden Patterns

Category Banned
Visual Neon glows, pure black (#000), pure neutral grays (always tint them), gray text on a colored background, oversaturated accents, gradient text on headers, custom cursors
Typography Inter font, oversized H1s, Serif on dashboards
Layout 3-column equal card rows, floating elements with awkward gaps, cards nested inside cards
Components Default shadcn/ui without customization

1.6 Creative Arsenal

Category Patterns
Navigation Dock magnification, Magnetic button, Gooey menu, Dynamic island, Radial menu, Speed dial, Mega menu
Layout Bento grid, Masonry, Chroma grid, Split-screen scroll, Curtain reveal
Cards Parallax tilt, Spotlight border, Glassmorphism, Holographic foil, Swipe stack, Morphing modal
Scroll Sticky stack, Horizontal hijack, Locomotive sequence, Zoom parallax, Progress path, Liquid swipe
Gallery Dome gallery, Coverflow, Drag-to-pan, Accordion slider, Hover trail, Glitch effect
Text Kinetic marquee, Text mask reveal, Scramble effect, Circular path, Gradient stroke, Kinetic grid
Micro Particle explosion, Pull-to-refresh, Skeleton shimmer, Directional hover, Ripple click, SVG draw, Mesh gradient, Lens blur

1.7 Bento Paradigm

  • Palette: Background #f9fafb, cards pure white with border-slate-200/50
  • Surfaces: rounded-[2.5rem], diffusion shadow
  • Typography: Geist/Satoshi, tracking-tight headers
  • Labels: Outside and below cards
  • Animation: Spring physics (stiffness: 100, damping: 20), infinite loops, React.memo isolation

5-Card Archetypes:
1. Intelligent List — auto-sorting with layoutId
2. Command Input — typewriter + blinking cursor
3. Live Status — breathing indicators
4. Wide Data Stream — infinite horizontal carousel
5. Contextual UI — staggered highlight + float-in toolbar

1.8 Brand Override

When brand styling is active:
- Dark: #141413, Light: #faf9f5, Mid: #b0aea5, Subtle: #e8e6dc
- Accents: Orange #d97757, Blue #6a9bcc, Green #788c5d
- Fonts: Poppins (headings), Lora (body)

1.9 Pre-Build Truth File & Revision Vocabulary

① 事前落一份项目真值文件。 Phase 1 收尾时,把「受众 / 品牌调性 / 硬约束 / 明确不做什么」写进项目根 PRODUCT.md,
视觉方向另记 DESIGN.md,之后每次改动都回读它。理由:设计偏好一旦只存在于对话里,第二个会话就找不回来;
落成文件后可复读、可 diff、可交接。与 §1.8 的分工:§1.8 是跨项目固定的品牌覆盖,这里是单项目真值。

② 用动词下指令,不要用形容词。 「这块不好看」= AI 只能瞎猜;「这块 quieter」= 有确定的改动方向。
可用动词集(借自 Impeccable 的 23 命令,按用途分四组):

组 动词 用途
收紧 distill / quieter / harden 去冗余、降噪、补错误态与边界
加强 bolder / colorize / delight / overdrive 提层级、上色、加记忆点
整形 typeset / layout / animate / adapt 字体层级、间距节奏、动效、多端
过关 critique / audit / polish / optimize UX 评审、技术质量、出货前终检、性能

用法示例:首页 hero 段 bolder 一点 / 注册流程 harden / 整体 distill,现在太满。

③ 评审分两轨,不要混谈。 可机器判的(颜色对比、溢出、断点、DOM 结构)走 B 篇 §5 九项校验;
只能人判的(层级、克制、情绪)走 critique 这类主观评审。
混在一起谈,就会出现「用技术指标证明好看」这种无效结论。

④ 反模式不能只写在纸上,要能查。 §1.5 的表里,下列几条可 grep 直接抓:
#000(纯黑)、Inter(字体)、无 tint 的中性灰字面量(gray-500 / neutral-500 一类,
应改用带色相的 slate / stone / zinc)。
卡片套卡片改查 DOM 层级更可靠 —— 见 B 篇 §5 第 1 项(源码层面合法,grep 抓不到)。
「彩色底上的灰字」需要计算样式对比度,归 B 篇 §7 目检。


2. Motion Engine

2.1 Tool Selection Matrix

Need Tool
UI enter/exit/layout Framer Motion — AnimatePresence, layoutId, springs
Scroll storytelling (pin, scrub) GSAP + ScrollTrigger — frame-accurate control
Looping icons Lottie — lazy-load (~50KB)
3D/WebGL Three.js / R3F — isolated &lt;Canvas&gt;, own &quot;use client&quot; boundary
Hover/focus states CSS only — zero JS cost
Native scroll-driven CSS — animation-timeline: scroll()

Conflict Rules [MANDATORY]:
- NEVER mix GSAP + Framer Motion in same component
- R3F MUST live in isolated Canvas wrapper
- ALWAYS lazy-load Lottie, GSAP, Three.js

2.2 Intensity Scale

Level Techniques
1-2 Subtle CSS transitions only, 150-300ms
3-4 Smooth CSS keyframes + Framer animate, stagger ≤3 items
5-6 Fluid whileInView, magnetic hover, parallax tilt
7-8 Cinematic GSAP ScrollTrigger, pinned sections, horizontal hijack
9-10 Immersive Full scroll sequences, Three.js particles, WebGL shaders

2.3 Animation Recipes

See references/motion-recipes.md for full code. Summary:

Recipe Tool Use For
Scroll Reveal Framer Fade+slide on viewport entry
Stagger Grid Framer Sequential list animations
Pinned Timeline GSAP Horizontal scroll with pinning
Tilt Card Framer Mouse-tracking 3D perspective
Magnetic Button Framer Cursor-attracted buttons
Text Scramble Vanilla Matrix-style decode effect
SVG Path Draw CSS Scroll-linked path animation
Horizontal Scroll GSAP Vertical-to-horizontal hijack
Particle Background R3F Decorative WebGL particles
Layout Morph Framer Card-to-modal expansion

2.4 Performance Rules

GPU-only properties (ONLY animate these): transform, opacity, filter, clip-path

NEVER animate: width, height, top, left, margin, padding, font-size — if you need these effects, use transform: scale() or clip-path instead.

Isolation:
- Perpetual animations MUST be in React.memo leaf components
- will-change: transform ONLY during animation
- contain: layout style paint on heavy containers

Mobile:
- ALWAYS respect prefers-reduced-motion
- ALWAYS disable parallax/3D on pointer: coarse
- Cap particles: desktop 800, tablet 300, mobile 100
- Disable GSAP pin on mobile < 768px

Cleanup: Every useEffect with GSAP/observers MUST return () =&gt; ctx.revert()

2.5 Springs & Easings

Feel Framer Config
Snappy stiffness: 300, damping: 30
Smooth stiffness: 150, damping: 20
Bouncy stiffness: 100, damping: 10
Heavy stiffness: 60, damping: 20
CSS Easing Value
Smooth decel cubic-bezier(0.16, 1, 0.3, 1)
Smooth accel cubic-bezier(0.7, 0, 0.84, 0)
Elastic cubic-bezier(0.34, 1.56, 0.64, 1)

2.6 Accessibility

  • ALWAYS wrap motion in prefers-reduced-motion check
  • NEVER flash content > 3 times/second (seizure risk)
  • ALWAYS provide visible focus rings (use outline not box-shadow)
  • ALWAYS add aria-live=&quot;polite&quot; for dynamically revealed content
  • ALWAYS include pause button for auto-playing animations

2.7 Dependencies

npm install framer-motion           # UI (keep at top level)
npm install gsap                    # Scroll (lazy-load)
npm install lottie-react            # Icons (lazy-load)
npm install three @react-three/fiber @react-three/drei  # 3D (lazy-load)

3. Asset Generation

3.1 Scripts

Type Script Pattern
TTS scripts/minimax_tts.py Sync
Music scripts/minimax_music.py Sync
Video scripts/minimax_video.py Async (create → poll → download)
Image scripts/minimax_image.py Sync

Env: MINIMAX_API_KEY (required).

3.2 Workflow

  1. Parse: type, quantity, style, spec, usage
  2. Craft prompt: Be specific (composition, lighting, style). NEVER include text in image prompts.
  3. Execute: Show prompt to user, MUST confirm before generating, then run script
  4. Save: &lt;project&gt;/public/assets/{images,videos,audio}/ as {type}-{descriptor}-{timestamp}.{ext} — MUST save locally
  5. Post-process: Images → WebP, Videos → ffmpeg compress, Audio → normalize
  6. Deliver: File path + code snippet + CSS suggestion

3.3 Preset Shortcuts

Shortcut Spec
hero 16:9, cinematic, text-safe
thumb 1:1, centered subject
icon 1:1, flat, clean background
avatar 1:1, portrait, circular crop ready
banner 21:9, OG/social
bg-video 768P, 6s, [Static shot]
video-hd 1080P, 6s
bgm 30s, no vocals, loopable
tts MiniMax HD, MP3

3.4 Reference

  • references/minimax-cli-reference.md — CLI flags
  • references/asset-prompt-guide.md — Prompt rules
  • references/minimax-voice-catalog.md — Voice IDs
  • references/minimax-tts-guide.md — TTS usage
  • references/minimax-music-guide.md — Music generation (prompts, lyrics, structure tags)
  • references/minimax-video-guide.md — Camera commands
  • references/minimax-image-guide.md — Ratios, batch

4. Copywriting

4.1 Core Job

  1. Grab attention → 2. Create desire → 3. Remove friction → 4. Prompt action

4.2 Frameworks

AIDA (landing pages, emails):

ATTENTION:  Bold headline (promise or pain)
INTEREST:   Elaborate problem (&quot;yes, that&#x27;s me&quot;)
DESIRE:     Show transformation
ACTION:     Clear CTA

PAS (pain-driven products):

PROBLEM:    State clearly
AGITATE:    Make urgent
SOLUTION:   Your product

FAB (product differentiation):

FEATURE:    What it does
ADVANTAGE:  Why it matters
BENEFIT:    What customer gains

4.3 Headlines

Formula Example
Promise "Double open rates in 30 days"
Question "Still wasting 10 hours/week?"
How-To "How to automate your pipeline"
Number "7 mistakes killing conversions"
Negative "Stop losing leads"
Curiosity "The one change that tripled bookings"
Transformation "From 50 to 500 leads"

Be specific. Lead with outcome, not method.

4.4 CTAs

Bad: Submit, Click here, Learn more

Good: "Start my free trial", "Get the template now", "Book my strategy call"

Formula: [Action Verb] + [What They Get] + [Urgency/Ease]

Place: above fold, after value, multiple on long pages.

4.5 Emotional Triggers

Trigger Example
FOMO "Only 3 spots left"
Fear of loss "Every day without this, you're losing $X"
Status "Join 10,000+ top agencies"
Ease "Set it up once. Forget forever."
Frustration "Tired of tools that deliver nothing?"
Hope "Yes, you CAN hit $10K MRR"

4.6 Objection Handling

Objection Response
Too expensive Show ROI: "Pays for itself in 2 weeks"
Won't work for me Social proof from similar customer
No time "Setup takes 10 minutes"
What if it fails "30-day money-back guarantee"
Need to think Urgency/scarcity

Place in FAQ, testimonials, near CTA.

4.7 Proof Types

Testimonials (with name/title), Case studies, Data/metrics, Social proof, Certifications


5. Visual Art

Philosophy-first workflow. Two output modes.

5.1 Output Modes

Mode Output When
Static PDF/PNG Posters, print, design assets
Interactive HTML (p5.js) Generative art, explorable variations

5.2 Workflow

Step 1: Philosophy Creation

Name the movement (1-2 words). Articulate philosophy (4-6 paragraphs) covering:
- Static: space, form, color, scale, rhythm, hierarchy
- Interactive: computation, emergence, noise, parametric variation

Step 2: Conceptual Seed

Identify subtle, niche reference — sophisticated, not literal. Jazz musician quoting another song.

Step 3: Creation

Static Mode:
- Single page, highly visual, design-forward
- Repeating patterns, perfect shapes
- Sparse typography from canvas-fonts/
- Nothing overlaps, proper margins
- Output: .pdf or .png + philosophy .md

Interactive Mode:
1. Read templates/viewer.html first
2. Keep FIXED sections (header, sidebar, seed controls)
3. Replace VARIABLE sections (algorithm, parameters)
4. Seeded randomness: randomSeed(seed); noiseSeed(seed);
5. Output: single self-contained HTML

Step 4: Refinement

Refine, don't add. Make it crisp. Polish into masterpiece.


Quality Gates

Design:
- [ ] Mobile layout collapse (w-full, px-4) for high-variance designs
- [ ] min-h-[100dvh] not h-screen
- [ ] Empty, loading, error states provided
- [ ] Cards omitted where spacing suffices

Motion:
- [ ] Correct tool per selection matrix
- [ ] No GSAP + Framer mixed in same component
- [ ] All useEffect have cleanup returns
- [ ] prefers-reduced-motion respected
- [ ] Perpetual animations in React.memo leaf components
- [ ] Only GPU properties animated
- [ ] Heavy libraries lazy-loaded

General:
- [ ] Dependencies verified in package.json
- [ ] No placeholder URLs — grep the output for unsplash, picsum, placeholder, placehold, via.placeholder, lorem.space, dummyimage. If ANY found, STOP and replace with generated assets before delivering.
- [ ] All media assets exist as local files in the project's assets directory
- [ ] Asset prompts confirmed with user before generation

设计评审双角色:先「挑毛病」,再「动手改」(2026-09-21 立)

> 来源:视频号博主「蔓奇拉AI短视频」《两个 skills 提高你的审美》(2026-09-19,35 秒)|台账 §126。
> 原话主旨:「taste 负责挑毛病,impactable 负责改」,两个搭配用。
> 吸收的是分工方法,不是工具本身 —— 博主点名的两个外部 skill 未验证,不得当依赖;可替换为任何等价的"审查者 + 执行者"组合。

角色 干什么 硬要求
挑毛病 按大师标准审这一稿:平庸的版式、没信息量的排版,直接点出来并说明差在哪 只挑不改 —— 不提改法,避免"边审边改"导致自我合理化
动手改 拿毛病清单一次性改版式/配色/动效 说人话驱动(「标题放大」「颜色换暖色」),不让用户记参数

为什么必须分两个上下文:同一上下文里先审后改 ⇒ 审出来的问题会被自己"顺手"淡化。
本库已在别处验证过同一条纪律 —— 审查者与执行者不共享上下文(见 omni-skill-forge §B18「评委与选手分离」、§B17 四视角自省)。

何时用:凡有视觉交付的任务(网页/PPT/配图),在 Phase 6 之前加这一关。
判据:挑毛病必须产出可执行的毛病清单(每条写明「哪一处 → 差在哪」);
只输出「还不错/挺好的」⇒ 这一关等于没做,重跑。


React and Next.js are trademarks of Meta Platforms, Inc. and Vercel, Inc., respectively. Vue.js is a trademark of Evan You. Tailwind CSS is a trademark of Tailwind Labs Inc. Svelte and SvelteKit are trademarks of their respective owners. GSAP/GreenSock is a trademark of GreenSock Inc. Three.js, Framer Motion, Lottie, Astro, and all other product names are trademarks of their respective owners.


B 篇 · 网页验收(验)

一句话流程:
> 结构校验 → 渲染校验 → 交互验证(CDP)→ 视觉/截图核验 → (需要时)会话修复 → 批量填表 → 提交动作留给人工

何时用

场景 走哪几节
交付了一个自包含 HTML(官网/原型/大屏/汇报页) §1 环境坑 → §5 结构 → §6 文案 → §7 响应式 → §9 截图 → §12 清理 → §13 清单
要证明「页面渲染了、按钮点了有反应」 §2 决策树 → §3 渲染校验 → §4 哨兵 → §5 CDP
需要双端/多端预览页,或把设计板变成真机页面 §10 控制台 / §11 双端交付
「明明登录了但点了没反应」 §14 会话修复
一批作品要往已登录的网页表单里逐件填 §15 批量填表

决策树(三层验证的取用逻辑):本机有 playwright / agent-browser → 用它们,本技能的零依赖路线不必要;只想确认页面能打开、没有白屏 → 只做第②层渲染校验(5 分钟可跑通);要确认按钮真的有反应 → 做 ②+③(渲染校验 + CDP 点击)。Windows 自带 Edge,零安装即可覆盖全三层。

§0 三条铁律(违反即失败)

  1. 三层验证不可互替。 接口冒烟证明业务规则对,但推不出页面 JS 跑起来了;渲染校验证明各模块渲染、无 JS 异常,但推不出按钮点了有反应;只有 CDP 真实点击能证明交互。实测中三层各自抓到过对方抓不到的问题:渲染层抓到路由 off-by-one 整页空白;交互层抓到「表单 select 赋了无效值静默变空」。只做接口冒烟就宣称"已验证"= 把未验证风险留给用户。
  2. 改完源码必须回读确认。 同一文件连发多个 Edit 可能「报成功但没落盘」,产物里搜不到新关键字 = 没改上。故障集中在同一份页面、所有交互一起失效而其他页面全好 → 先怀疑源码没同步,不要先怀疑逻辑。
  3. 「提交审核」默认不点。 平台改版会让 selector 失效产生假提交;提交后进审核队列人看不到驳回原因;独家授权/分成比例这类字段必须人工把关。自动化到提交前一步停下。

第一部分:静态页面交付校验

§1 本机环境工具链(先看这段省时间)

项 正确做法
Node ~/.workbuddy/binaries/node/versions/22.22.2-3/node.exe(注意 -3 不是 -2)
Python 基础版无 PIL。要图像处理用 venv:~/.workbuddy/binaries/python/envs/default/Scripts/python.exe
Edge C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe
Bash 工具 默认 PATH 下 coreutils 报 command not found(ls/grep/sed/head/wc/cat/find)。先试一行修复:export PATH=&quot;/usr/bin:/bin:$PATH&quot; —— 2026-09-22 实测 ls / mkdir / mv / cp / head / grep / sed / awk 全部恢复可用,能省掉绕道 Node 的大半功夫;修复无效时再退回 Node(fs.readdirSync / split().join()),删文件用 fs.rmSync。rm 是坏的 safe-delete shim 必失败
cd/重定向/for/绝对路径调 exe 正常可用
PowerShell 可用但 stdout 不回传——所有结果必须落盘再 Read。&amp; $exe args *&gt; file 写出的是 UTF-16,Read 判为二进制 → 让被调脚本自己 writeFileSync(path, text, &#x27;utf8&#x27;)

内容替换脚本模式(改文案不要用几十次 Edit):

const fs=require(&#x27;fs&#x27;); const F=&#x27;page.html&#x27;;
let h=fs.readFileSync(F,&#x27;utf8&#x27;); let miss=0;
function P(a,b){ const n=h.split(a).length-1;
  if(!n) miss++;
  console.log((n?&#x27;✓&#x27;:&#x27;✗ MISS&#x27;)+&#x27; (&#x27;+n+&#x27;) &#x27;+a.slice(0,60));
  h=h.split(a).join(b);
}
fs.writeFileSync(F,h,&#x27;utf8&#x27;); console.log(&#x27;MISS =&#x27;, miss);

一次跑完上百处替换,MISS=0 才算过关。比逐个 Edit 快一个数量级且可核验。

替换失败时,先 grep 出实际行,不要猜匹配串(2026-09-22 实测)。
一次题库修订中按「选项原文」定位待改行,三条全部 MISS —— 真实原因是上一轮已经把选项顺序打散,
原文串早已不在。改按「不随打散变化的关键词」(如选项内容里的特征词)定位,一次命中。
看见 MISS 的第一动作是 grep 真实行做对比,第二动作才是改脚本;直接改脚本=对着想象中的文件调参。

§5 九项结构校验

用 scripts_qa/validate.js(改 FILE 常量即跑)。覆盖:

  1. 标签闭合——栈式匹配,跳过 &lt;script&gt;/&lt;style&gt; 内容与 void 元素
  2. CSS 括号平衡——&lt;style&gt; 内 {} 计数
  3. 内联脚本语法——vm.Script 逐个编译(只看语法不看执行)
  4. 图标 def/ref 一一对应——&lt;g id=&quot;i-xxx&quot;&gt; 定义数 vs &lt;use href=&quot;#i-xxx&quot;&gt; 引用数,报未定义与未使用
  5. 外部资源计数——http(s)://、//cdn、@import、&lt;link href&gt;,目标 0(离线双击可开)
  6. 内部锚点无死链——href=&quot;#x&quot; 都能找到 id=&quot;x&quot;
  7. 结构计数——Tab 数 == 面板数、卡片数、section 数(防区块被误删)
  8. 旧文案/违规词残留(见 §6)
  9. 重复整句扫描

> 校验「违规词残留为 0」时必须先剔除 HTML 注释:h.replace(/&lt;!--[\s\S]*?--&gt;/g,&#x27;&#x27;)。否则文件头「已删除表述」的留痕说明会被误报为残留。

整卡链接 + 卡内链接的坑
这套结构校验自身的三条已知坑(2026-09-22 实测,改校验脚本前先读):

  1. 图标 def/ref 与内部锚点检查,会被「JS 动态拼接的引用」误报。页面若用
    &#x27;&lt;use href=&quot;#ic-&#x27; + x + &#x27;&quot;&gt;&#x27; 这类模板拼引用,静态正则扫出的片段会被同时报成
    「未定义的图标」和「死链」。修法:把含 + 的 href 排除出静态扫描,再从数据里取真实值
    (icon:\s*&#x27;([^&#x27;]+)&#x27;)补进引用集合,两边求并 —— 不要为了让它变绿而去改页面。
  2. 数组型内联数据末尾插新元素,必须给原末项补逗号。漏掉直接造成 Unexpected token &#x27;{&#x27;,
    整页 JS 停摆、所有交互一起失效,而其他静态检查全是绿的。这不是假想:一次真实交付中
    前两项检查全绿,正是第 3 项「内联脚本语法」把它抓住的 —— 旁证铁律 1:三层验证不可互替。
  3. 验收断言不要硬编码会变化的常量。实测一手:题库版本升级后存档键由 …-v1 变 …-v2,
    断言里写死的 getItem(&#x27;…-v1&#x27;) 立即误报「存档失败」。改为前缀匹配
    (k.indexOf(&#x27;前缀-&#x27;) === 0)或从页面实时取值,从此与版本解耦。
    :整张卡片包进 &lt;a class=&quot;card&quot;&gt; 后卡内再加 &lt;a&gt; 会触发解析器锚点嵌套拆分——外层锚点被强制闭合,卡片 DOM 拆散、grid 子项错位(缩略图与文字分家)。校验脚本的"标签闭合"查不出(源码层面合法)。对策:卡片用 div + position:relative,末尾放一个绝对定位铺满整卡的透明 &lt;a class=&quot;cover&quot;&gt;(position:absolute;inset:0;z-index:1)做点击层,卡内其它链接 position:relative;z-index:2 浮在其上。布局异常时用 --dump-dom 或截图目检确认 DOM 树没被解析器重排。

§6 文案类校验三个专项

① 撞句扫描:可见文本抽出(去注释、去 &lt;style&gt;/&lt;script&gt;/&lt;svg&gt;),按 。!?\n 切句,长度 ≥8 出现 ≥2 次就报。跨区撞句最要命。

② 口径一致性:客户改的是定位/口径而非单句时,先列「与目标口径冲突的词表」,全站替换后校验残留为 0。关键:改口径必须连数据一起改,否则自相矛盾——文案说「每小区独立」而数字写「一个平台管 28,000 人」是穿帮。清单:KPI 数字、图表 stroke-dasharray/offset(环形图 stroke-dashoffset = C × (1 − 占比),C = 2πr;只改文字不改 offset 会出现「文字 34% 圆弧画 68%」)、进度条 width、列表数量、脚注统计口径。

③ 日期/时效性:所有 20\d\d 出现处、© 版权年、更新日志、公告条、SINCE xxxx 统一到客户访问时的当下年份。绝对化/不可自证表述(「首个」「深耕 N 年」「1,200+ 客户」)→ 先检索是否已被他人主张;不可用则换可被验证的规则口径(「12 项功能」「30 分钟开通」)。

事实核实前置:页面上任何可被外部验证的表述,写之前先 WebSearch 核实。核实结论写进文件头 HTML 注释留痕(记明「已删除 X,原因 Y」)——留痕必须放注释里,且校验残留词时先剔除注释。

§7 多断点响应式巡检 + 基线对比法(本技能最有价值的一招)

不要只看 documentElement.scrollWidth。 要列出所有 scrollWidth &gt; clientWidth 的元素,然后对改动前的备份跑同一个探针,两份清单逐条比对。分不清「我搞坏的」和「本来就这样」时这一步是唯一解。

scripts_qa/measure.js 两段式(原因见 §8 Edge 启动限制):

# 第 1 步:生成探针副本 + 采集脚本(含改动前基线)
node scripts_qa/measure.js prepare &lt;当前页.html&gt; &lt;改动前备份.html&gt; --widths 1440,1280,1024,767,500,390
# 第 2 步:PowerShell 工具执行生成的 _pqa_run.ps1
# 第 3 步:解析 + 基线比对
node scripts_qa/measure.js analyze --widths 1440,1280,1024,767,500,390
  • 清单完全一致 → 没引入新问题,收工
  • 多出 N 项 → 这轮引入的,去查那 N 个元素

溢出项要甄别,不能一律当 bug:装饰性光斑用负边距(right:-130px)超出容器、被父级 overflow:hidden 裁掉,是设计内行为(超出量恰好等于负边距值)。探针输出 ov= 是 overflow-x 首字母:h=hidden / v=visible / a=auto。ov=h 且超出量等于某个负边距 → 设计内;ov=v 且超出量大 → 真溢出。横向滚动轨(ov=h/a 的 marquee 行)也不是缺陷。

> --window-size 有下限:请求 500 和 390 得到同一个视口(492)——Edge 把窗口宽钳制约 492px,两档截图字节完全相同(md5 可证,正常现象)。真测 390 要用 iframe 对照板(§11)或 DevTools 协议模拟。

§8 Edge 无头环境:启动限制大全(最耗时的坑都在这)

  1. --headless 由 Bash 工具子进程树启动会静默退出:退出码 0、用时 0s、stdout 0 字节。execFileSync 直连、stdio 绑 fd、cmd /c &gt; file 全无效。唯一可行路径:用 PowerShell 工具启动 Edge(从 Bash 里调 PowerShell 会被安全策略拦截)。
  2. Edge 153+ launcher 立即返回,渲染在后台完成:&amp; $edge --headless ... --screenshot=$out 几百毫秒内返回,此刻 $out 不存在、$LASTEXITCODE 是空的。照旧逻辑会误判"截图失败"白折腾十几轮。必须 &amp; 调用后 Start-Sleep -Seconds 15~22 再 Test-Path。判定顺序:只看 Test-Path $out,不要看 $LASTEXITCODE。 --headless 与 --headless=new 在 153 上都可用,等待与否才是成败分界。
  3. 🚫 不要用 Start-Process 启动 Edge:直接返回 exit -1,进程树被拦下。改用 &amp; 调用运算符。遇到 exit -1 不要调参数,是启动方式错了。
  4. 🚫 优先内联命令,不要写 .ps1 再 &amp; script.ps1 调用:已验证代码放进 .ps1 后调用返回 exit 1 且脚本体完全没执行。同一段代码内联下发即一次通过。
  5. dump-dom 取结果用 bash 重定向,不靠 PowerShell 捕获:PowerShell 里 --dump-dom 只会得到 5 字节空文件(GUI 子系统 stdout 不挂管道),而 --screenshot 写文件所以"看起来正常",极易误判成"页面没执行脚本"。
EDGE=&quot;/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe&quot;
&quot;$EDGE&quot; --headless=new --disable-gpu --no-sandbox --virtual-time-budget=6000 \
        --dump-dom &quot;file:///C:/path/page.html&quot; &gt; out.dom 2&gt;/dev/null
  1. --user-data-dir 三坑:① Git Bash 里用正斜杠路径静默失败(退出码 21),直接传 Windows 反斜杠;② 不给独立目录会与已运行实例争抢配置目录,随机失败;③ 指向已存在的配置目录会复用现有实例不生成新文件(症状=新旧 PNG 字节数完全相同)→ 每次截图换新 profile,并核对 LastWriteTime + 字节数。
  2. 省掉清理:用 Get-Random 造唯一 temp 目录,一次解决 profile 复用、无需 Remove-Item、目录名唯一三件事:
$tag = &#x27;dp&#x27; + (Get-Random); $tmp = Join-Path $env:TEMP $tag
New-Item -ItemType Directory -Path $tmp -Force | Out-Null
  1. --window-size 会偶发不生效:同一份页面同一组参数,截图「整体放大+内容溢出」而代码完全正常——是那次 Edge 没应用窗口尺寸,视口退回默认小尺寸走了窄屏分支。换算关系:innerWidth = --window-size 宽 − 24,innerHeight = − 92(1440,900 → 视口 1416×808)。判断视口必须用实测值。先探针、再改码,不要反复改 CSS。

自证探针(比 document.title + --dump-dom 稳——部分版本 dump-dom 不执行注入脚本):把关键值画到页面上(固定定位黑底绿字大字块),截一张图直接读,一次到位:

setTimeout(function(){
  var st=document.querySelector(&#x27;.stage&#x27;), kc=document.querySelector(&#x27;.kpi__c&#x27;);
  var r=st.getBoundingClientRect();
  var o=document.createElement(&#x27;div&#x27;);
  o.style.cssText=&#x27;position:fixed;left:0;top:0;z-index:9999;background:#000;color:#0f0;font:15px/1.5 monospace;padding:10px;white-space:pre&#x27;;
  o.textContent=[
    &#x27;innerWH = &#x27;+innerWidth+&#x27; x &#x27;+innerHeight+&#x27;   dpr=&#x27;+devicePixelRatio,
    &#x27;stage.rect = &#x27;+Math.round(r.width)+&#x27; x &#x27;+Math.round(r.height),
    &#x27;hdr.cols = &#x27;+getComputedStyle(document.querySelector(&#x27;.hdr&#x27;)).gridTemplateColumns
  ].join(String.fromCharCode(10));
  document.body.appendChild(o);
},1600);

判据:stage.rect 缩放后应正好等于视口、hdr.cols 应为多列、卡片宽应为预期值。三者都对就是页面没问题,是截图环境的锅——重截一次即可,不要动 CSS。
9. 同一轮循环里连续多次启动 Edge 易返回空(profile 竞争)→ 每个断点用全新的 --user-data-dir。
10. 读 PowerShell 落盘日志不要用非贪婪正则:PS 5.1 Set-Content -Encoding UTF8 写 BOM。正确写法:

txt = open(log, encoding=&#x27;utf-8-sig&#x27;).read().replace(&#x27;\x00&#x27;,&#x27;&#x27;)
d = [l for l in txt.splitlines() if l.startswith(&#x27;TMP=&#x27;)][0][4:].strip()
  1. 中文文件名不要拼 [char]0xXXXX:PowerShell 命令串里的中文会踩编码坑(曾把「潘」U+6F58 写成「潜」U+6F5C,Edge 打开错误页,截图 65 KB 而正常 720 KB)。正确做法:让 Node 把目标页复制到临时目录改名纯 ASCII,再让 Edge 开那个路径。 判据:拿到异常小的图先怀疑打开的是错误路径。

§3 第②层:无头渲染校验(三层验证之中层)

原理:--headless=new --dump-dom 让浏览器真实加载页面、执行 JS,把最终 DOM 打到 stdout(不是文件——subprocess.run 后去读某文件路径,那文件从来不存在,用 capture_output=True 读 p.stdout)。

最小可用命令:

EDGE=&quot;/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe&quot;
&quot;$EDGE&quot; --headless=new --disable-gpu --no-sandbox --no-first-run \
  --user-data-dir=&quot;$TEMP/wb_profile&quot; --virtual-time-budget=8000 \
  --dump-dom &quot;http://127.0.0.1:8770/#dashboard&quot; &gt; dom.html

--virtual-time-budget=8000 是关键:把定时器快进,等异步渲染完成再 dump。

断言三件事:
1. 无 JS 异常——靠哨兵(§4),不靠文本匹配
2. 关键内容出现——每模块挑 2-4 个只有它才有的字符串
3. 骨架完整——侧栏项数量 == 模块总数

第 3 条的隐蔽陷阱:dom.count() 统计的是 DOM 字符串子串不是 CSS 选择器。元素渲染后形如 &lt;a class=&quot;nav-item active&quot;&gt;,必须写 dom.count(&#x27;class=&quot;nav-item&#x27;);写 &#x27;.nav-item&#x27; 只在 &lt;style&gt; 里命中,全部模块误报"骨架不完整"。模板见 scripts_qa/render_check_template.py,改 EXPECT 与 SKELETON_MARKER 复用。

§4 JS 异常哨兵(渲染校验的可靠性前提)

问题:前端通常有 try/catch 包住渲染函数,但事件回调里的异步异常和未捕获 Promise 拒绝不会留痕——dump 出的 DOM 看起来正常,实际点了就报错。

解法:前端入口加全局哨兵,异常写进隐藏 DOM 节点,dump 后正则匹配(片段见 scripts_qa/js_error_guard.js):

function markJSError(m){
  var n=document.createElement(&#x27;div&#x27;); n.id=&#x27;__jserror__&#x27;;
  n.style.display=&#x27;none&#x27;; n.textContent=m; document.body.appendChild(n);
}
window.addEventListener(&#x27;error&#x27;, e =&gt; markJSError(e.message));
window.addEventListener(&#x27;unhandledrejection&#x27;, e =&gt; markJSError(&#x27;&#x27;+e.reason));

必须先做反向测试证明断言不是空转:造一个 setTimeout(()=&gt;{throw new Error(&#x27;BOOM-TEST&#x27;)},10) 的页面,确认 DOM 里真的出现 __jserror__。没做这步,「无异常」断言等于没断言。

§5b 第③层:CDP 真实点击

CDP 就是 Chrome DevTools 背后的协议,playwright 也基于它;装不了运行时或用户明确偏好零依赖时它是唯一出路。Python 标准库 socket 手写最小 WebSocket 客户端(约 90 行)连调试端口发 JSON-RPC,完整实现见 scripts_qa/cdp_min.py,直接 import。

流程:

browser = subprocess.Popen([
    EDGE, &quot;--headless=new&quot;, &quot;--remote-debugging-port=9333&quot;,
    &quot;--user-data-dir=&quot; + PROFILE, &quot;--no-first-run&quot;, &quot;about:blank&quot;])
# 轮询 http://127.0.0.1:9333/json/version 拿 webSocketDebuggerUrl
# 用 /json/new?url=... 开标签页,拿该 tab 的 ws url
cdp = CDP(ws, session)
cdp.call(&quot;Runtime.evaluate&quot;, {&quot;expression&quot;: &quot;document.title&quot;, &quot;awaitPromise&quot;: True,
                              &quot;returnByValue&quot;: True})

关键参数:Runtime.evaluate 必须带 awaitPromise: True,否则异步函数返回 Promise 对象而非结果。

断言写法:一次 evaluate 里把"操作 + 取结果"都做完,比拆多次 CDP 调用稳:

(async () =&gt; {
  const w = ms =&gt; new Promise(r =&gt; setTimeout(r, ms));
  const el = document.querySelector(&#x27;#submit&#x27;);
  if (!el) return {ok: false, info: &#x27;按钮不存在&#x27;};
  el.click(); await w(1200);
  const txt = document.querySelector(&#x27;.modal-b&#x27;)?.innerText || &#x27;&#x27;;
  return {ok: /OK|WARN/.test(txt), info: txt.slice(0, 60)};
})()

交互测试两个硬性前提:

一、必须用真实素材文件。涉及文件校验的功能(读扩展名、解析图片头部、算体积),传不存在的路径必然被拦下,测不出真实结论。用标准库现造真 PNG:

import struct, zlib
def write_png(path, w, h):
    raw = b&#x27;&#x27;.join(b&#x27;\x00&#x27; + b&#x27;\xc8\xb4\xa0&#x27; * w for _ in range(h))
    def chunk(t, d):
        c = t + d
        return struct.pack(&#x27;&gt;I&#x27;, len(d)) + c + struct.pack(&#x27;&gt;I&#x27;, zlib.crc32(c))
    open(path, &#x27;wb&#x27;).write(b&#x27;\x89PNG\r\n\x1a\n&#x27;
        + chunk(b&#x27;IHDR&#x27;, struct.pack(&#x27;&gt;IIBBBBB&#x27;, w, h, 8, 2, 0, 0, 0))
        + chunk(b&#x27;IDAT&#x27;, zlib.compress(raw)) + chunk(b&#x27;IEND&#x27;, b&#x27;&#x27;))

二、每次运行加唯一 token。有 SHA-256 查重或唯一性约束的系统,第二次跑同样测试数据会被判重复而非通过。给标题内容拼 uuid4().hex[:8] 或时间戳。

§5c 幂等与状态污染

状态 症状 处理
限速时钟 后续触发全部 throttled 持久化到 DB,并给测试提供重置点
每日配额 当天提交数用尽 测试段临时抬高,断言段改回
触发周期标记 同周期只触发一次 显式重置接口
测试残留数据 查重命中、统计偏移 finally 里清理,按测试标记前缀删

顺带的真实缺陷教训:限速时钟只存内存——重启服务即可绕过限速护栏。护栏状态不能只放在内存,持久化既是测试需要更是合规需要。

Windows 两个环境坑:
- 端口重复绑定:SO_REUSEADDR 允许多进程 bind 同一端口,"bind 成功 = 端口空闲"的探测在 Windows 失效——旧进程继续处理请求,表现「改了代码却没生效」。必须 connect 探测:

s = socket.socket(); s.settimeout(0.5)
try:
    s.connect((&#x27;127.0.0.1&#x27;, port)); return True   # 有人在用
except OSError:
    return False
finally:
    s.close()
  • 随机主键不能用于排序:主键用 uuid4 之类随机串,ORDER BY id DESC LIMIT 3 得到的是字典序抽样不是最近 3 条。取"最近 N 条"必须 ORDER BY at DESC, rowid DESC。这个坑会让「连续 3 次驳回触发熔断」时灵时不灵。

统一入口:scripts_qa/verify_all_template.py 演示三层编排,任一层失败即非零退出。改完代码跑一条命令即可。

§9 整页截图与预览图

  1. 生成「截图副本」:&lt;/head&gt; 前注入覆盖样式,关掉干扰项:
&lt;style&gt;.fab,.totop,.ann{display:none!important}
.rv{opacity:1!important;transform:none!important;transition:none!important}
*{animation:none!important;transition:none!important}
html{scroll-behavior:auto!important}&lt;/style&gt;

.rv 是滚动入场动画类,不关掉截图空白;.fab/.totop 悬浮件会压在内容上。
更省事的替代:页面 CSS 已自带 @media (prefers-reduced-motion: reduce) 降级时,直接给 Edge 加 --force-prefers-reduced-motion,无需副本。先 grep 确认页面有 prefers-reduced-motion 再用。

  1. --window-size=1440,&lt;足够高&gt; + --hide-scrollbars 截整页。必须 PowerShell 工具执行(§8),&amp; 后必须 Start-Sleep -Seconds 20 再查文件,每张图换新 profile 并核对 LastWriteTime+字节数。

  2. PIL 检测实际内容底边再裁掉多余空白:

import numpy as np
from PIL import Image
a = np.array(Image.open(p).convert(&#x27;RGB&#x27;))
bg = np.array([249,252,255])          # ← 基准色取法见下
d = np.abs(a.astype(int)-bg[None,None,:]).sum(axis=2)
nz = np.nonzero(d.max(axis=1) &gt; 10)[0]
end = int(nz.max()) + 1

底边检测五坑:
- 背景基准色必须从 HTML 的 :root/body 背景声明读取,不要从截图任意角落采样——页面顶部满宽深色区块时取 a[0,0] 拿到的是区块色,之后全部误判(曾把「内容高 10018、窗口 20000」的正常图误判成截断)。取法:$NODE -e &quot;const t=require(&#x27;fs&#x27;).readFileSync(&#x27;page.html&#x27;,&#x27;utf8&#x27;);console.log((t.match(/--bg:\s*(#[0-9A-Fa-f]{3,8})/)||[])[1])&quot;
- position:fixed 悬浮件把底边顶到窗口底部 → 扫描时排除该元素横坐标区间(如只扫 x &lt; 55% 宽)。修前报 13200,真实底边 11382。
- 扫描范围必须覆盖全图,初值设 0 会在未命中时静默返回 0(裁出 46px 废图)——初值 -1 或显式报错;先按步长 4 粗扫再逐行精扫;「整行纯色」判据留容差(相邻采样差值累计 > 3×tol),否则渐变区块误判。
- 窗口高度不足被静默截断:症状是「内容底边 == 请求窗口高 == 图像高」三者相等,直接加大高度重截(5600 → 9600),不当成"页面正好这么高"。一个 40KB、8 section 的表格型说明页 1400 宽下实测高 8303px,同量级按 9600 起截。
- vh 高度页:min-height:100vh 的 hero 在 19000px 截图窗口会被撑到 1.7 万像素全错位 → 先 grep vh,存在则生成截图副本注入 &lt;style&gt;.hero{min-height:auto!important}&lt;/style&gt;;深色主题页面裁剪基准改用页面深底色。

  1. 输出:整页预览 / 移动端预览 / 首屏 三张。截图副本隐藏公告条后页面上移约 44px——按坐标像素采样时要补偏移。

§9b 面板级探针(大屏 / 固定版式页)

面板级溢出探针(替代"肉眼看有没有被裁切"):

var bad=[];
document.querySelectorAll(&#x27;.panel&#x27;).forEach(function(p){
  var t=p.querySelector(&#x27;.panel__hd h3&#x27;); var nm=t?t.textContent.replace(/\s+/g,&#x27;&#x27;):&#x27;?&#x27;;
  var bd=p.querySelector(&#x27;.panel__bd&#x27;); if(!bd) return;
  var dh=bd.scrollHeight-bd.clientHeight, dw=bd.scrollWidth-bd.clientWidth;
  if(dh&gt;2||dw&gt;2) bad.push(nm+&#x27;[h+&#x27;+dh+&#x27;,w+&#x27;+dw+&#x27;]);
});
var de=document.documentElement;
document.title=&#x27;PROBE|page &#x27;+de.scrollWidth+&#x27;x&#x27;+de.scrollHeight+&#x27;|win &#x27;+innerWidth+&#x27;x&#x27;+innerHeight+
  &#x27;|OVF &#x27;+(bad.length?bad.join(&#x27; ; &#x27;):&#x27;none&#x27;);

三条判据同时成立才算版式过关:page 尺寸 == win 尺寸;固定版式屏的 .stage 高度 == 视口高度(16:9 骨架没被撑开);OVF none。探针输出务必过滤成纯 ASCII(replace(/[^A-Za-z0-9._-]/g,&#x27;&#x27;))——DOM 转义叠加后中文变实体,正则失配。

实测收益:一屏 6 个 KPI 卡片报 [h+7](内容比容器高 7px,截图几乎看不出,换字体就崩)。修法优先"减结构"而不是"缩字号"——把卡片第二行小字并进第一行,省一整行高度。

把探针从 .panel 扩到自定义卡片(大屏常有 .svc 服务卡等自绘卡片),顺带把容器自身尺寸写进标题。装饰元素造成溢出假阳性别急着动布局:负偏移(right:-34px)→ 溢出量恰好等于偏移(w+34);装饰尺寸大于宿主内框 → 溢出量 = 装饰高 − 内框高。都被 overflow:hidden 裁掉肉眼无感。修法是把装饰收进宿主(缩小尺寸、偏移归零),不是改布局。

容器"空壳"检测:把关键容器 id 列成清单,检查 id=&quot;X&quot; 到下一个 id=&quot; 之间的内容长度。曾靠它发现渲染函数整段被漏掉(DOM 里只剩 &lt;div id=&quot;devCards&quot;&gt;&lt;/div&gt;)——只数 class、只看截图都发现不了。

改完源码回读确认(重复铁律 2):同一条消息对同一文件连发多个 Edit,两处报成功但文件没变 → getElementById(&#x27;新容器&#x27;) 拿到 null 抛错中断整个 IIFE,同屏 10 个抽屉全报"未渲染"、地图图表全空——看起来像"改版改坏了",其实源码没改上。处置:每次 Edit 后立刻检索回读;"产物中搜不到新关键字"是最快定位手段;对比同名字符串计数分辨"改错了"还是"没改上"。

固定版式页(数据大屏/汇报页):靠整体缩放,不靠断点重排:

const vh = Math.max(h, 900), k = h / vh;          // 以 900px 为虚拟高度
st.style.height = vh + &#x27;px&#x27;;
if (k &lt; 0.999) {
  st.style.width = Math.ceil(w / k) + &#x27;px&#x27;;        // 先放大再缩,栅格比例不变
  st.style.transformOrigin = &#x27;top left&#x27;;
  st.style.transform = &#x27;scale(&#x27; + k.toFixed(4) + &#x27;)&#x27;;
}

窄屏重排断点要压得足够低:实测 1180px 会在 1440 视口误触发(把大屏排成纵向),应取 ≤900px。这段写错的症状和「截图环境故障」长得一模一样——先探针再改码。

交互层批量验收(覆盖"点进去才渲染"的抽屉/弹层):首屏 DOM 里抽屉正文本来就是空的,dump 首屏等于没验。为每个抽屉生成"自动打开"副本:

&lt;script&gt;window.addEventListener(&#x27;load&#x27;,function(){
  setTimeout(function(){ try{ openDrawer(&#x27;s1-opr&#x27;) }catch(e){ document.title=&#x27;JSERR:&#x27;+e.message } },250);
});&lt;/script&gt;

逐个 --dump-dom,断言:标题无 JSERR:、抽屉容器带打开态类名、正文 > 200 字、统计分区与图表数量。一屏 20 个抽屉跑完不到一分钟。

IntersectionObserver 空白坑:无头 + --virtual-time-budget 下 IntersectionObserver 不派发 → .rv 元素停在 opacity:0,整页截图「只有公告条+导航+大片背景」。opacity 不影响布局所以页高是对的,极易误判"页面写坏了"。症状:1440×12000 的图只有 184 KB。对策:副本注入 .rv{opacity:1!important},改后 1.59 MB 正常。文件大小是最快的判据。

§10 多端预览控制台

需求「生成桌面端+移动端最终预览页」:最优解是一个控制台页面,把各源页 base64 内联进去(不是多个文件、不是互 src= 的 iframe)。

为什么必须 base64 + srcdoc:&lt;iframe src=&quot;sibling.html&quot;&gt; 单独拷走就白屏,且算外部依赖。源页含成百上千个 &quot;(实体转义体积膨胀极易漏转)、含 &lt;script&gt; 的页放不进 JS 字符串。base64 字母表不含任何危险字符,一次性解决全部转义:

function b64ToText(b64){
  var bin = atob(b64), bytes = new Uint8Array(bin.length);
  for (var i = 0; i &lt; bin.length; i++) bytes[i] = bin.charCodeAt(i);
  return new TextDecoder(&#x27;utf-8&#x27;).decode(bytes);   // 直接 atob 会乱码,必须过 TextDecoder
}
wf.setAttribute(&#x27;srcdoc&#x27;, b64ToText(WEB_B64));

占位符要双向断言:替换前断言每个占位符恰好出现 1 次,替换后断言无残留——否则静默漏替换生成空白预览页,而校验脚本查不出来(标签照样闭合、0 外部请求)。

浏览器外框 + 视口切换器(一个 iframe 演 5 档断点):

function fitWeb(){
  var avail = view.clientWidth, h = view.clientHeight;
  var k = Math.min(1, avail / curW);
  wf.style.width = curW + &#x27;px&#x27;;
  wf.style.height = (h / k) + &#x27;px&#x27;;          // 缩放后正好等于容器高
  wf.style.transform = &#x27;scale(&#x27; + k + &#x27;)&#x27;;
  wf.style.left = Math.max(0, (avail - curW * k) / 2) + &#x27;px&#x27;;
}

关键:只改 iframe 的 CSS 宽度即可触发内部文档重新布局,不需重设 srcdoc——切换零延迟。容器 overflow:hidden。

手机外框 + 单屏切换器:设计稿类源页(一页平铺 N 台手机)不能直接塞进 375px 框(手机套手机)。往 srcdoc 里再注入一层样式把目标屏以外的全部隐藏,body.only-N 切屏 = 重设 srcdoc。关键取舍:用「重设 srcdoc」而不是 contentDocument.body.className——重设 srcdoc 是纯字符串操作 100% 可靠,别为了省一次重解析去赌同源。不要给自有内容的 iframe 加 sandbox——那是给自己加失败面。

验收:headless 截不了交互:生成一份改了初始值的测试副本(字符串替换 + 断言命中:curW=1440→460、showScreen(1)→showScreen(7)),截图看徽标与手机内容是否变化。一个截图周期换到「交互确实可用」的硬证据。

§11 双端独立交付:设计板 → 真机页面

场景:手上是移动端设计板(固定 375×812 画框排 8 张静态稿),要的是能在手机上直接跑的独立页面。设计板 ≠ 可运行页面,这一步是重写。

四条改造原则:
1. 不自绘状态栏。真机状态栏由系统绘制,页面只处理 env(safe-area-inset-top);&lt;meta name=&quot;viewport&quot; content=&quot;...,viewport-fit=cover&quot;&gt; 是 env(safe-area-inset-*) 生效前提,漏了永远是 0。
2. 不自绘客户端胶囊。微信小程序胶囊由客户端绘制,页面只在导航右侧预留 96px 安全位;系统返回手势区不放可点元素。
3. Tab 根页不放返回箭头。Tab 根页在真机上返回没有目标。改左侧品牌标识 + 标题 + 右侧操作。
4. 两套 Tab 栏用 hidden 切换,不经 JS 渲染图标——比 JS 按端拼 innerHTML 少一半出错点。

外壳骨架(可直接抄):

.app{max-width:430px;margin:0 auto;min-height:100vh;min-height:100dvh;
  display:flex;flex-direction:column;padding-top:env(safe-area-inset-top)}
.view{flex:1}
.screen{display:none}.screen.on{display:flex;flex-direction:column}
.mp-nav,.ap-nav{position:sticky;top:calc(var(--swh) + env(safe-area-inset-top));z-index:20}
.tabs{position:sticky;bottom:0;z-index:25;
  padding:5px 0 calc(5px + env(safe-area-inset-bottom))}   /* 安全区在 padding 里,不在 height 里 */

max-width:430px 一物两用:手机全铺、桌面居中成一列手机宽度。原型专用的「端切换条」必须显式标注(如「原型预览 · 切换端」),否则会被当成产品界面。

验收一:自检看板法(headless 点不了按钮但可以注入脚本替用户点):给副本注入脚本 → 跑断言(.click() 驱动真实交互再读 DOM)→ 结果渲染成页顶深色文本条 → 整页截图 → 读图取结果。比 --dump-dom 可靠(PowerShell stdout 不回传是常态)。本轮 9 条:端切换 / Tab 栏随端切换 / Tab 切换 / 激活态唯一 / chips 互斥 / seg 互斥 / 按压反馈 / 清红点 / 跨端 Tab 记忆。

验收二:逐元素横向溢出检测:scrollWidth 在容器有 overflow 时测不出内部溢出。临时把外壳钉到目标宽度,逐元素比右边界(横向滚动容器必须排除否则误报),覆盖 320 与 430 两档 × 全部屏。触控热区同理遍历(≥44px)。

验收三:接触表 + 真机宽度对照板:接触表用 !important 把多屏页覆写成 flex 网格,脚本给每张卡克隆对应底部导航置好激活态——一次截图拿 8 屏,同时是可交付总览图。再做一版干净副本(不跑交互断言),否则图里留着测试后状态。真机宽度对照板:外框 HTML 里并排若干真实 iframe(320/375/430),是绕过 --window-size 下限约 492px 的唯一稳妥办法;用 #app 锚点让不同 frame 停在不同状态——一张图同时证明宽度自适应 + 端切换。

生成器脚本不要在开头 rmSync 整个临时目录——会把上一轮已截好的 PNG 一起删掉反复重截。只删自己产出的文件。

§16 像素级复核(读不了图时的替代方案)

Read 一个 PNG 可能返回「模型不支持图片」。不要因此跳过视觉验证,改用坐标采样:
1. 先从探针取目标元素绝对坐标(getBoundingClientRect().top + scrollY 及 left/width/height)
2. PIL 在矩形内采样:深色面板 lum &lt; 120 占比(应 > 85%);浅色卡片 lum &gt; 170 占比;品牌渐变文字 (max-min) &gt; 60 的高饱和占比(区分渐变字 vs 黑字)
3. 判定「区块是否空壳」:几何宽高是否为 0、卡片是否等高、多列是否等宽

能可靠回答「深色面板画出来了没 / 卡片是不是空的 / 渐变字有没有渲染成黑色」。

§12 交付前清理

浏览器 --user-data-dir 目录文件数以千计:Node fs.rmSync 40–55 秒/个;cmd /c rd /s /q 55–70 秒/个(并不更快)。37 个配置目录累计约 40 分钟。最佳策略是根本不在项目目录里生成(进系统临时目录、逐轮即删,scripts_qa/measure.js 已按此实现)。已堆在项目目录的:用 run_in_background 跑清理,不要前台等(超 120 秒被 SIGTERM 打断且只删掉前几个)。

保留可追溯性:改动前的备份改名保留、不删(xxx_备份_本轮改动前.html);备份含已知问题文案时文件名写明 _勿外发。

§13 交付物清单模板

xxx.html                    ← 主交付,0 外部资源,双击可开
xxx_整页预览.png            ← 桌面端,按实际内容高裁切
xxx_移动端预览.png
xxx_首屏.png
overview.md                 ← 设计说明 + 校验结论 + 取舍留痕
xxx_备份_本轮改动前.html     ← 仅对比用

要展示多端表现时再加预览控制台(§10):

xxx_双端预览与适配说明.html   ← 控制台:内联各源页 + 设备外框 + 切换器 + 适配标注
preview-build/
  preview-template.html      ← 控制台外壳(含 __WEB_B64__ 占位符)
  build.js                   ← node build.js 重新内联并落盘
  crop-preview.py            ← 截图裁切

控制台是生成物:源页更新后重跑 build.js 同步(页脚注明"当前为生成时的快照")。模板与构建脚本必须一起交付。最后 present_files 一次带上全部。


第二部分:SPA 会话修复与批量填表

§14 Cookie → LocalStorage 反写恢复会话

场景(国内 SPA 站点最常见的灵异现象):顶栏有头像显示"已登录";任何受保护操作(提交/上传/收藏/评论)点击 0 网络请求 0 console 输出 0 fetch 0 XHR;页面不跳不报错不弹"请登录"。

根因(已踩坑验证):SPA 把"账号鉴权"和"业务鉴权"分开存——账号层在 cookie(znzmo-id、accountId、gr_session_id 等),业务层在 localStorage/sessionStorage(userInfo、signloginSuccessFlag、loginTypeSuccess 等)。SPA 写"清空登录态"时本该 removeItem,写错成 setItem(&#x27;userInfo&#x27;, &#x27;undefined&#x27;) → 字符串 &quot;undefined&quot; 落进 storage。JSON.parse(&#x27;undefined&#x27;) 抛 SyntaxError 被 try/catch 兜成 null 时表象正常;一旦用 if (localStorage.userInfo) 这种 truthy 判定,&quot;undefined&quot; 是 truthy → 毒数据被当有效数据传到后端 → 401/403,但前端不发请求、不报错、不弹窗。

检测信号(一行命令):

JSON.stringify({
    userInfo: String(localStorage.userInfo),
    userInfoLen: (localStorage.userInfo||&#x27;&#x27;).length,
    cookie: (document.cookie.match(/userInfo=([^;]+)/)||[])[1] &amp;&amp; decodeURIComponent(RegExp.$1)
})

特征值:&quot;undefined&quot; / &quot;null&quot; 字符串毒数据;userInfoLen === 0 字段被清但 cookie 还在;cookie 解析后有 {accountId:&quot;xxx&quot;} —— cookie 还在就有救。

运行前提(restore_session.py 三条):① 本机 Edge/Chrome 已用独立 profile 开 --remote-debugging-port=9333;② 当前 page 类型 tab 至少有一个目标站;③ CDP 探测时 localStorage/cookie/document.cookie 可读(同源)。

恢复流程(5 步,全程 CDP 零依赖,脚本 scripts_qa/restore_session.py):
1. CDP 取 ws URL → Page.enable + Runtime.enable
2. evaluate 检测脚本,确认 cookie 还有 userInfo
3. evaluate 内联 JS:解码 cookie 的 userInfo → 重新编码为 JSON → localStorage.setItem(&#x27;userInfo&#x27;, JSON) → 同时写 signloginSuccessFlag、loginTypeSuccess(知末是 3 个键,先在原站 console 跑一遍 storage keys 列出才知道要写哪几个)
4. Page.reload({ignoreCache: true}) 强制刷 SPA
5. evaluate 验证:解析 localStorage.userInfo 有 accountId + 页面没 .notLoginBox + title 正确

后续:恢复成功后不要直接 click 受保护按钮——React in-memory state 还没更新。走 reload + 等 SPA 异步初始化(2-3s)+ 确认 .notLoginBox 不存在,再调业务脚本。建议把"恢复 localStorage"打包进业务脚本入口校验:脚本启动先跑检测,发现毒数据自动恢复并提示用户"会话刚静默过期,已自动恢复"。

复用范围:知末(znzmo.com)、站酷、小红书创作中心、千牛、淘宝商家后台、抖音创作服务平台、公众号后台——任何"账号在 cookie,业务态在 localStorage"的中文站点。

反模式:
- 不要直接 setItem(&#x27;userInfo&#x27;, &#x27;硬编码 JSON&#x27;)——必须先 decode cookie 拿真实 accountId
- 不要只写 userInfo 一个键——不同站需要写的 key 数量不同
- 不要用 setTimeout 替代 reload——React store 是 in-memory
- 不要信任 document.title 或"看起来已登录"——那些用 cookie 判定,业务 storage 失活仍能显示

一句话心法:localStorage.userInfo === &#x27;undefined&#x27; 是 SPA 平台「我又把登录态写坏了」的红绿灯。看见立即警觉、按本节跑恢复流程。

§15 CDP 批量填表

它解决什么:一批作品(十几到几十件)要在已登录的网页表单(创作者中心、知末、站酷、Behance…)逐件发布。工作流到「点提交审核」前主动停手:

自动化 留给人工
选择分类 提交审核按钮
上传 zip / 图片 内容审核敏感字段(分成比例、广告定向)
选价格默认值 验证码 / 短信 / 人脸验证
写入描述、标签 —

决策树:

要把 N 件作品按顺序自动填入某网页表单
├─ 浏览器已登录、目标页面已开着? → 复用现有 CDP 会话(ws://127.0.0.1:9333/devtools/page/&lt;TABID&gt;)
├─ 表单结构已知? → 是:直接写 auto_fill.py;否:先写探针摸清每个字段的 selector 与 wait 条件
├─ 表单是 antd / 自研组件 / React 受控?
│   ├─ antd → 标准 .ant-form-item selector
│   ├─ 自研级联(CSS Modules 哈希类名)→ 不能套标准 antd 组件 API
│   └─ React 受控 textarea → 必须用 React 友好的 native value setter
└─ 平台用 antd message 池化 toast? → 判断上传完成绝不能用 body.innerText 关键词匹配

七条核心经验(必读):

  1. 表单可能在可滚动容器里,window.scrollTo 无效。长表单 DOM 是 body &gt; div.layout &gt; div.scrollable,所有表单项在最内层 overflow:auto 容器里;视口外的 .ant-select-selector.click() 点了没反应。修法:找到 overflow:auto/scroll 的父容器调它的 scrollTop(脚本:从表单项向上找第一个滚动容器,sp.scrollTop += fr.top - sr.top - sr.height/2 + fr.height/2,找不到再 scrollIntoView)。

  2. 分类往往是自研组件,不是标准 antd Cascader。CSS Modules 哈希类名(如 .ClassifyInfo__classifyItemText___zEDQ)没有 .ant-cascader-menu。修法:JS 直接 tg.click() 并校验 active 类出现;失败重试一次;跨级点击父级激活 → 子级才生效。

  3. React 受控 textarea 不能直接 el.value = txt——绕过 React 的 onChange,state 没更新,submit 时取不到值。必须原生 setter:

const desc = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(ta), &#x27;value&#x27;);
if (desc &amp;&amp; desc.set) desc.set.call(ta, txt); else ta.value = txt;
ta.dispatchEvent(new Event(&#x27;input&#x27;, {bubbles: true}));
ta.dispatchEvent(new Event(&#x27;change&#x27;, {bubbles: true}));
  1. antd Tag 的 Enter 提交需要完整 keydown → char → keyup 三段:只发 keyDown+keyUp 不被 antd 视为回车提交;keyDown 后再发一次 type: char 事件(附 text:&#x27;\r&#x27;、windowsVirtualKeyCode:13)。Input.insertText 之后先清残留(setSelectionRange(0, value.length) + Backspace),否则再次 insertText 出现 "aabb" 重复串。

  2. 「上传完成」绝不能看 document.body.innerText:antd message 池化 toast 的历史文案残留 30+ 秒,innerText.includes(&#x27;AI智能识别&#x27;) 永远为真 → wait 永远超时,而图片其实已上传、CDN src 都返回了。修法:用上传 slot 数量 + 每个 slot 里 &lt;img&gt; src 是否形如 https://... 判定。

  3. 多张图禁止重复内容:平台有去重,「图片重复」会被拒。3 张不能都从中心裁——从左/中/右或上/中/下三段不同区域裁,先 LANCZOS 缩放至长边 ≤ 2400px 控体积。

  4. 不要被"selector 还在"骗——用 DOM.setFileInputFiles 替换上传:每跑下一件作品,从干净状态起步是错的(要重新登录);从已上传状态通过 set_file_input 覆盖同一 selector 才稳——平台 React onChange 会 reset 当前表单进入新作品发布流。注意先关闭所有确认 modal(如「确认删除」红色按钮是自研类名不是 ant-btn)。

三段配套脚本结构:

your_project/
├── manifest.json                 # seq, title, tags[], desc, cover_path, archive_path
├── tools/
│   ├── auto_fill.py              # 单条填充主程序,--seq N
│   ├── batch_run.py              # 顺序执行多条,--seqs &quot;3,4,5&quot;(失败即停)
│   ├── patch_seqN.py             # 单条修补(只补需要补的字段,从 auto_fill 导入函数)
│   └── probe_*.py                # 诊断探针
├── upload-prep/
│   ├── fill_imgs/                # 4:3 裁剪后的图片
│   ├── screenshots/              # 验收截图
│   └── sigs/                     # 完成标记 .flag

诊断探针(批量跑完后某条 flag 没写、或怀疑只填了一半):dump 每个可见 .ant-form-item 的 label / select 值 / 上传 slot 数 / 标签 chips,不要漏 textarea(描述通常没有 label)。模板见 scripts_qa/probe_form_status_template.py。

「提交审核」按钮的处理原则(重申铁律 3):默认不点。平台改版 selector 失效时 false-positive「提交成功」最危险;提交后进审核队列人无法立即看到驳回原因;「独家授权」「分成比例」必须人工把关。用户坚持要点:先探针拿按钮坐标 click_xy,且必须 --confirm-submit 参数确认。

局限:富文本/所见即所得编辑器超出适用范围;上传体积受平台限制时提前压缩校验;验证码/短信/人脸必须人工,不绕过;一个 manifest 通常配同一种品类/授权策略,混搭要分桶。

与三层验证的关系:§3–§5 是「前端能不能用」的三层验证;本节是它的应用层——先用三层验证证明目标平台的 demo 表单可填,再把单条 demo 扩成多条批量。共用同一份 cdp_min.py。

§17 脚本清单

文件 用途 来源
scripts_qa/cdp_min.py 最小 WebSocket + CDP 客户端,直接 import 复用 verify
scripts_qa/js_error_guard.js 前端异常哨兵片段 verify
scripts_qa/render_check_template.py 渲染校验模板(改 EXPECT 即可) verify
scripts_qa/verify_all_template.py 三层编排入口 verify
scripts_qa/validate.js 九项结构校验 页面校验
scripts_qa/measure.js 多断点溢出探针(prepare/analyze 两段式) 页面校验
scripts_qa/restore_session.py cookie→localStorage 会话恢复 resync
scripts_qa/auto_fill_zhimo.py 单条填表主程序实例 form-filler
scripts_qa/batch_run_template.py 顺序批量驱动 form-filler
scripts_qa/probe_form_status_template.py 表单 dump 探针 form-filler

§18 能力边界

  • 无头渲染 ≠ 真实视觉呈现:布局错位、颜色异常、字体缺失测不出来(要靠 §9 截图 + §16 采样补位)。三层验证解决"能不能用",不是"好不好看"。
  • 人机验证、登录态、短信流程无头跑不通,仍需人工。
  • CDP 协议随浏览器版本演进,本实现覆盖 Runtime.evaluate / Page.navigate / Input 等常用域,够做功能断言,不适合性能剖析。
  • 在线文档/表格走 tencent-docs 系列;需要从零造页面的正向开发走本技能 A 篇(网页开发)。

附:融合说明

本技能由四部技能于 2026-09-18 融合而成(取其精魄、去其糟粕):
- 交付-页面校验(static-page-delivery-qa)——第一部分主体(§1、§5–§13)
- zero-dep-frontend-verify——三层验证骨架(§0 铁律 1、§2–§5c、§17)
- cookie-localstorage-resync——SPA 会话修复(§14)
- cdp-batch-form-filler——批量填表(§15)
合并了两处重复的「与 zero-dep-frontend-verify 的关系」与「环境工具链坑」;「一句话流程」由 resync 源的心法扩展为全链路引言。原技能已归档于 skills_archive/。2026-09-19 本篇再与「网页开发(A 篇)」「登录巡检(C 篇)」融合为《万象-网页开发与验收》。


C 篇 · 登录巡检(守)

前置判断(先做,再动手)

按顺序排除,任何一条不成立就别上这套方案:

  1. 平台有没有官方 API 或用户态令牌? 有 → 走官方通道,不要做浏览器自动化。
    平台若提供令牌工具(如 connect_open_platform),先看它的调用是否被当前技能授权。
  2. 能不能驱动客户端自身界面? 如果目标是宿主应用自己的界面(如 WorkBuddy 客户端),
    agent-browser 打不开它,必须走网页版。
  3. 登录态能否持久化? 关键校验项:CLI 是否支持 --profile &lt;目录&gt;。
    不支持就只能同 session 内保持,关掉即失效,定时化无从谈起。
  4. 动作是否幂等可判定? 必须能从页面文案/属性读出「当前处于哪种状态」,
    否则无法保证「一天只做一次」。

五步骨架

① 装 agent-browser  →  ② 人工登录一次(有头)  →  ③ 探针导出页面结构
                                                    ↓
                  ⑤ 挂定时(RRULE 拆条)    ←   ④ 写幂等巡检脚本

① 安装与内核

# 装到隔离工作区,不要全局装
cd &lt;node-workspace&gt; &amp;&amp; npm install agent-browser --no-audit --no-fund

官方内核下载(agent-browser install)在受限网络下经常失败(大文件被限速/阻断,而 doctor 的
HEAD 探测却返回 200,很有迷惑性)。不必死磕——直接用本机已装的浏览器内核:

--executable-path &quot;C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe&quot;

依赖代价:目标机器必须保持该浏览器安装,且要把这件事写进交付说明。

② 人工登录一次

AB --profile &quot;$PROFILE&quot; --executable-path &quot;$EDGE&quot; --headed --idle-timeout 0 open &quot;&lt;目标URL&gt;&quot;
  • --idle-timeout 0 关闭空转退出,避免你还没登录完浏览器就关了。
  • 登录完成后 close,Cookie/localStorage 留在 $PROFILE 目录里,之后可无头复用。
  • 验证持久化是否真的成立:close 掉守护进程,再以无头模式打开,
    确认页面是已登录状态(这一步别跳过,很多失败都出在这)。

③ 探针导出页面结构

写一个一次性 probe.sh,把每一步输出重定向到独立文件(见坑 1),再逐份读:

G open  &quot;$URL&quot;          &gt; &quot;$OUT/01-open.txt&quot;   2&gt;&amp;1 &lt; /dev/null
G wait  6000            &gt; /dev/null 2&gt;&amp;1        &lt; /dev/null
G snapshot              &gt; &quot;$OUT/02-page.txt&quot;   2&gt;&amp;1 &lt; /dev/null
G screenshot &quot;C:/abs/path/shot.png&quot; &gt; &quot;$OUT/03-shot.txt&quot; 2&gt;&amp;1 &lt; /dev/null

要拿到的东西:

  • 登录态判据:页面里一个只在登录后出现的文案(用于「未登录即中止」)。
  • 状态判据:优先找主按钮的文案差异——按钮文案往往就是状态机,读文字即可判定,
    不必点开弹窗、更不必调接口。这是整个方案最省事的突破口。
  • 弹窗内容在 a11y 快照的末尾,不在触发按钮旁边,解析时要扫全文。
  • 稳定 ref=eNN 每次刷新都会变 → 不要硬编码 ref,每次现取:
ref_of() { grep &quot;$1&quot; &quot;$2&quot; | grep -o &#x27;ref=e[0-9]*&#x27; | sed -n &#x27;1p&#x27; | sed &#x27;s/ref=//&#x27;; }

④ 幂等巡检脚本

见下方模板。核心是「状态判定 → 动作映射 → 日期标记防重复」。

⑤ 挂定时

见「定时化」一节。

五个致命坑(全部实测踩过)

坑 1(最耗时):绝不能把 agent-browser 的输出接进管道

# ✗ 死法:无任何输出 + Signal: SIGTERM,缓冲区全丢,看起来像&quot;静默崩溃&quot;
agent-browser open &quot;$URL&quot; | sed -n &#x27;1,20p&#x27;
agent-browser open &quot;$URL&quot; | grep xxx

# ✓ 正确:重定向到文件,并显式关掉 stdin
agent-browser open &quot;$URL&quot; &gt; out.txt 2&gt;&amp;1 &lt; /dev/null

原因:CLI 会拉起一个常驻守护进程,该进程继承了管道的写端;只要守护进程活着,管道就永不 EOF,
sed/grep 一直等 → 整组进程最后被 SIGTERM 掉,缓冲区内容全丢。
表现是「完全没输出」,极易误判成安装失败或权限问题。 决定性识别方法:
同一条命令换成写文件就成功、走管道就失败。

坑 2:失败运行会残留僵尸守护进程,导致连环失败

一次失败之后,后续所有调用都会失败(连接被拒 / 静默挂起),因为旧守护进程占着状态。
每次独立运行前后都要清理:

ab_cleanup() {
  local p
  for p in $(tasklist /FO CSV /NH 2&gt;/dev/null | grep -i &quot;agent-browser&quot; | cut -d&#x27;,&#x27; -f2 | tr -d &#x27;&quot;&#x27;); do
    taskkill //F //PID &quot;$p&quot; &gt;/dev/null 2&gt;&amp;1
  done
  rm -f ~/.agent-browser/default.* 2&gt;/dev/null
}

排查用状态文件:~/.agent-browser/{default.pid,default.port,default.engine}。

坑 3:守护进程活着时,启动参数被忽略

已存在守护进程时再次调用并传 --profile / --executable-path,这些参数会被忽略(只告警),
于是你以为换了配置、实际还在用旧的。改配置前先 close --all,或直接先 ab_cleanup。

坑 4:--session &lt;name&gt; 会引发失败

实测该参数会导致启动失败,不要用。多任务场景改用不同的 --profile 目录来隔离。

坑 5:Windows 下截图路径必须用 C:/... 风格

Unix 风格 /c/Users/... 会报 os error 3。Windows 路径统一写 C:/...(正斜杠,可被接受)。

附:本机 PATH 可能已损坏

若 dirname/head/sed 报 command not found,是 shell 初始化的 PATH 有问题。
每条 bash 命令都要显式 export PATH:

export PATH=&quot;/c/Users/&lt;用户&gt;/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/c/Windows/System32:/c/Windows:$PATH&quot;

幂等脚本模板

#!/usr/bin/env bash
source &quot;&lt;目录&gt;/ab-lib.sh&quot;          # PATH / AB / PROFILE / EDGE / LOG / ab_cleanup / ab_log
MODE=&quot;${1:-report}&quot;                # report=只检测  live=真实执行;默认停在 report
STAMP_FILE=&quot;$BASE/last_dispatch.date&quot;
TODAY=&quot;$(date &#x27;+%Y-%m-%d&#x27;)&quot;

ab_cleanup
G open &quot;$TARGET_URL&quot; &gt; &quot;$OUT/01-open.txt&quot; 2&gt;&amp;1 &lt; /dev/null
G wait 6000          &gt; /dev/null 2&gt;&amp;1     &lt; /dev/null
G snapshot           &gt; &quot;$OUT/02-page.txt&quot; 2&gt;&amp;1 &lt; /dev/null

# 1) 登录态闸门:未登录立刻中止,不要重试
if ! grep -q &#x27;&lt;登录后才有的文案&gt;&#x27; &quot;$OUT/02-page.txt&quot;; then
  ab_log &quot;FAIL 未登录,本轮中止(不重试)&quot;; G close &gt;/dev/null 2&gt;&amp;1; ab_cleanup
  echo &quot;STATE=not_logged_in ACTION=abort&quot;; exit 3
fi

# 2) 状态判定:读主按钮文案(顺序敏感,先判特殊态)
if   grep -q &#x27;&lt;已回来按钮&gt;&#x27; &quot;$OUT/02-page.txt&quot;; then STATE=&quot;arrived&quot;
elif grep -q &#x27;&lt;进行中按钮&gt;&#x27; &quot;$OUT/02-page.txt&quot;; then STATE=&quot;traveling&quot;
elif grep -q &#x27;&lt;空闲按钮&gt;&#x27;   &quot;$OUT/02-page.txt&quot;; then STATE=&quot;idle&quot;
else STATE=&quot;unknown&quot;; fi

# 3) 日期标记:保证一天只做一次
DONE_TODAY=&quot;no&quot;; [ -f &quot;$STAMP_FILE&quot; ] &amp;&amp; [ &quot;$(cat &quot;$STAMP_FILE&quot;)&quot; = &quot;$TODAY&quot; ] &amp;&amp; DONE_TODAY=&quot;yes&quot;

# 4) 动作映射
case &quot;$STATE&quot; in
  arrived)   ACTION=&quot;would_claim&quot;  ;;                              # live 时点击领取
  traveling) ACTION=&quot;wait&quot;         ;;                              # 什么都不做
  idle)
    if [ &quot;$DONE_TODAY&quot; = &quot;yes&quot; ]; then ACTION=&quot;skip_daily_limit&quot;   # ★ 防重复的关键
    else ACTION=&quot;would_dispatch&quot;; fi ;;                            # live 时两步点击
  *)         ACTION=&quot;none&quot;         ;;
esac

G close &gt;/dev/null 2&gt;&amp;1 &lt; /dev/null; ab_cleanup
ab_log &quot;state=$STATE mode=$MODE action=$ACTION done_today=$DONE_TODAY&quot;
echo &quot;STATE=$STATE ACTION=$ACTION DISPATCHED_TODAY=$DONE_TODAY&quot;

设计要点:

  • 双模式:report 只检测、live 才动按钮。首次交付默认停在 report,
    让用户看到判定正确后再切 live。这既是安全阀,也是排查手段。
  • 日期标记文件(last_dispatch.date)是「一天一次」的唯一保证,不要靠页面判断。
  • 动作成功后才写标记,失败不写,留给下一次巡检兜底。
  • 状态判定顺序敏感:先判「已回来」,再判「进行中」,最后「空闲」。
  • 两步动作(如「开弹窗 → 确认」)要在脚本内连做,中间 wait。
  • 登录态失效 = 中止,不要循环重试,否则会反复触发风控。
  • 提交前用 bash -n script.sh 做语法检查。

定时化

不规则时间点必须一条一个

RRULE 的 BYHOUR 与 BYMINUTE 是笛卡尔积:BYHOUR=8,12,16,21;BYMINUTE=15,30,45,0
会生成 16 个时间点,不是 4 个。所以 08:15 / 12:30 / 16:45 / 21:00 只能拆成 4 条自动化。

FREQ=DAILY;BYHOUR=8;BYMINUTE=15
FREQ=DAILY;BYHOUR=12;BYMINUTE=30

巡检次数 ≠ 操作次数

设计多个时间点时,靠脚本的日期标记保证真实动作只发生一次,多出来的时点只是「兜底」。
必须在交付说明里讲清楚这一点,否则用户会以为你要重复操作 N 次。

自动化 prompt 必须写死的四件事

未来运行时看不到当前对话,所以:

  1. 完整的、可照抄的命令(含显式 export PATH),并注明「只运行这一条,不要自己另开页面点」。
  2. 禁止把输出接管道(否则必然失败,且失败得毫无线索)。
  3. 结果读取方式用文件读取工具读日志最后一行,不要用 tail/cat。
  4. 模型与上下文窗口:连同 prompt 一起写死在说明里(第 4 条的由来见下)。

第 4 条的由来(2026-09-21 立)

> 来源:视频号博主「一启-AI」《WorkBuddy 更新避坑》(2026-09-20,58 秒)|台账 §134。

博主实测提醒:选 GM5.3 Flash/DeepSeek V4 Flash 这类大窗口模型时,上下文窗口的默认值已从 100 万降到 30 万(约三分之一),
要在模型选择处手动切回 100 万;并同理检查已有的定时任务,把任务里模型的上下文窗口也切到 100 万。

吸纳为两条纪律(不背书其具体数值,以本机界面实际为准):

# 纪律 理由
1 「长任务中途失忆/结论漂移」的第一排查点=上下文窗口配置 默认值随版本变,界面不提示
2 ⚠️ 不要把长任务的正确性押在窗口上:跨文件长链任务要把中间结论落盘,不靠会话记忆 窗口是配额,不是保证

这与本库反复验证的同一条纪律一致:不信默认值,回读实物。

检查清单

  • [ ] 已先排除官方 API / 用户态令牌通道
  • [ ] --profile 持久化登录态已实测(关掉重开后仍登录)
  • [ ] 每条命令都显式 export PATH
  • [ ] 脚本内部零管道,全部重定向到文件 + &lt; /dev/null
  • [ ] 每次运行前后 ab_cleanup
  • [ ] 状态判定基于实测,未验证的状态要如实标注
  • [ ] 日期标记文件生效,skip_daily_limit 可复现
  • [ ] bash -n 语法通过
  • [ ] 定时条数与时间点换算已用 date -d @&lt;时间戳&gt; 核对
  • [ ] 已向用户说明:页面改版即失效、需回来维护;登录态可能过期需重新登录
  • [ ] 已说明合规边界:自动化替代的是规则里「手动」的动作,是否开启由用户决定

D 篇 · 检索取证(取)

从"搜到"到"敢用"之间的工作流。搜索引擎给的是候选,不是答案——
尤其在中文技术圈,搜索结果里混杂着大量拼凑物、伪造署名和 SEO 垃圾。
本技能只解决一件事:把候选变成可追溯、可引用、经过核实的材料。

何时使用

  • 用户要找一份完整的文档/手册/规范(往往网络上只有二手拼凑版)
  • 需要判断某个广为流传的说法、某份资料的真伪
  • 抓取的页面返回 403 / 000,需要换通道
  • 准备把网上的长文拿去蒸馏、引用、写进报告(此时必须留痕)

核心立场:先查真伪,再取内容

不要一上手就抓全文。 先花两次调用确认这份东西到底是什么,否则可能花大力气
消化一份拼凑物。

真伪核查三问

  1. 署名人在一手渠道有吗?
    去作者/机构的官方站点、官方 GitHub、官方账号核。查不到就是红灯。
  2. 有没有人辟谣?
    直接搜"<资料名> 真相 / 辟谣 / 是真的吗"。内容农场型的资料通常已有核查文。
  3. 内容本身是真是假?
    关键区分——"署名假、内容真"非常常见。拼凑者把真实的一手资料重新包装、
    挂上权威名字。此时内容仍有价值,但署名必须纠正,且应回溯到真正的一手源。

内容农场识别特征(命中 2 条以上即高度可疑)

  • 标题堆砌关键词("全网疯传""真的太全了""XX页精编版")
  • 无作者、无 URL、无发布时间
  • 要求"关注公众号 / 后台回复关键词"领取
  • 把多个不同产品的概念混装进一个模糊容器名(如把 A 模型、B 工具、C 插件统称"CodeX")
  • 内容里夹杂大量截图转文字的乱码(说明是 OCR 拼的)

判定的四种结果

判定 处理
署名真 + 内容真 直接用,标注来源
署名假 + 内容真 可用,但纠正署名,回溯一手源替代二手整理
署名假 + 内容假 丢弃,告知用户
无法判定 标记 [待核实],不作为结论依据

抓取通道降级链(本机实测)

最重要的一条实测结论:本机 curl 与浏览器工具走的是不同网络通道,
curl 失败的 URL 用浏览器工具往往能成功。
不要因为 curl 返回 000 就判死刑。

优先级 通道 适用 实测表现
1 浏览器工具直接抓 绝大多数页面 ✅ 最可靠,优先用
2 浏览器工具抓 GitHub raw README / 配置文件 / 源码 ✅ 可用(curl 同 URL 却返回 000)
3 官方站 一手文档 ⚠️ 常见 403(WAF/地区限制)
4 curl 确认可直连的源 ⚠️ GitHub raw 常 000;官方站常 403
5 镜像(gitmirror / jsdelivr / ghproxy) GitHub 文件 ❌ 实测多返回 000 或 200 但 0 字节(假成功)
6 搜索引擎结果摘要 兜底 质量低但有时是唯一可得内容

警惕"假成功":镜像源常返回 HTTP 200 但 size_download = 0。
必须校验字节数,不能只看状态码。HTTP 000 = 连接层失败。

第 0 层:通道决定信源质量(2026-09-21 立)

> 来源:视频号博主「晓辉博士」的 CodeBuddy 日常用法分享(2026-09-19,8 分 26 秒)|台账 §124。
> 原话:「用了这样的搜索,你就可以提高信源的质量」,并点名 Serper(走谷歌)这个 MCP。

上表解决「同一个源用哪条路抓」;这一层解决「先选哪个源」——顺序不能反:
抓取技巧再好,源本身是内容农场也白搭(见本篇「内容农场识别特征」)。

⇒ 动手前先问一句:这次检索有没有可用的 MCP/专用数据源通道? 有则先用它定源,
再回上表选抓取方式。工具名与可用性以本机实测为准,不照抄博主。

抓正文的正确姿势

浏览器工具默认会总结内容,导致丢失细节。要原文时必须显式要求:

> 请完整输出这个文件的原文内容(不要总结,逐条保留,保留英文原样)。

对代码/配置文件尤其重要——总结版会丢掉字段名、默认值、边界条件。

工作流

阶段 1:发现(2-3 次搜索)

  • 用资料全名搜一次
  • 用"资料名 + 真相/辟谣"搜一次(真伪核查)
  • 用"一手源关键词 + 官方/GitHub/docs"搜一次

阶段 2:定级

对候选源按可信度排序,产出一张源清单:

| 源 | URL | 类型 | 可信度 | 备注 |
|---|---|---|---|---|
| 官方仓库 README | ... | 一手 | 高 | 权威,含安装与定位 |
| 官方 AGENTS.md | ... | 一手 | 高 | 工程规范原文 |
| 中文整理站 | ... | 二手 | 低 | 内容真但署名不实 |
  • 一手:官方站点、官方仓库、官方 CLI 的 --help、官方 schema
  • 二手:有署名的整理/教程
  • 三手:无署名聚合、内容农场、公众号搬运

同优先级下,选"离事实更近"的源:与其读别人整理的"手册",不如读官方仓库的
README.md + AGENTS.md + docs/——前者是转述,后者是源头。

阶段 3:取证

  • 按降级链抓正文
  • 每次抓取都记录:URL、通道、HTTP 状态、字节数、抓取时间、内容类型
  • 落地成文件,不要只留在对话里(对话会丢,文件能复用、能审计)

阶段 4:交叉验证

  • 任一事实性结论至少 2 个独立源佐证
  • 只有单一来源的,标注 [单源待核实]
  • 一手源与二手源冲突时,一律以一手源为准

阶段 5:交付

产出语料包目录:

&lt;输出目录&gt;/
├── README.md          # 抓到的一手资料原文
├── AGENTS.md          # (如有)工程/使用规范原文
├── SOURCES.md         # 源清单 + 可信度 + 抓取记录 + 真伪判定
└── notes/             # 过程中的中间判断

SOURCES.md 是必须的——没有它,下次无法判断这份语料还能不能用、要不要重抓。

证据记录模板

### 源 N:&lt;标题&gt;
- URL:&lt;完整 URL&gt;
- 类型:一手 / 二手 / 三手
- 通道:浏览器工具 / curl / 镜像 / 搜索摘要
- 抓取:2026-09-18 · HTTP 200 · 12,345 bytes
- 判定:署名真·内容真 | 署名假·内容真 | 署名假·内容假 | 待核实
- 用途:用于 ____ 结论
- 备注:&lt;任何保留意见、时效性问题&gt;

常见坑

  • 把搜索结果当结论:搜索结果第 1 条常常是 SEO 优化最好的,不是最准确的。
  • 只试一个通道就放弃:curl 挂了换浏览器工具,常能救回来。
  • 相信 200 状态码:务必看字节数,0 字节的 200 是假成功。
  • 抓二手不追一手:二手整理会引入错误、遗漏、版本滞后。能追一手就追。
  • 不留痕:两周后没人知道这份材料哪来的、还能不能用。
  • 忽略时效:AI 工具文档迭代极快,抓的时候记下日期,超过 3 个月要重新核。

与其他技能的边界

  • 需要登录才能看的页面 → 本技能 C 篇 · 登录巡检(同一部技能内,就近调用)
  • 拿到语料后要做成可复用能力 → 万象-知识蒸馏(本篇负责把材料取回来并留痕,
    仓颉负责把材料蒸馏成技能)
  • 只是读一个已知 URL 的一小段信息 → 直接用浏览器工具,不用本技能

附 · D 篇来源

本篇原为独立技能 万象-网页-检索取证(目录名 web-retrieval-verify),2026-09-19 吸收并入《万象-网页开发与验收》,成为第四篇。吸收前原文归档于 skills_archive/web-retrieval-verify__融合入万象-网页__20260919/。


E 篇 · 发布上线(发)

何时用

  • 页面/应用已经验收通过,用户要一个可分享的链接(「发出去」「上线」「给我个链接」)。
  • 用户要下线已发布的应用。

不适用:还没验收的开发中页面(先走 B 篇);只是本地预览(见 B 篇整页截图与预览图)。

§0 三条铁律

  1. 发布是外部动作,必须按本轮确认。 上一轮同意过不算 —— 每次都看本轮用户是否明确要求;没要求就先做完,再问要不要发。
  2. 不得把小程序转成网页来"发布成功"。 用户要的是小程序,网页不是小程序;把网页版部署上去,读起来像需求已满足,实际是替换了需求。写网页版另发属于新请求,须用户重新提。
  3. 只发布「单端口 HTTP 服务」。 沙箱只给一个 HTTP 端口,没有数据库、缓存、消息队列。依赖这些的项目发不出去,要如实说,不要强发。

§1 可发布的形态

形态 说明
静态站点 / PDF / 静态资源 内部 HTTP 服务直接托管,无需额外构建
可运行的 HTTP 应用(Node / Python / Go) 上传源码 → 沙箱装依赖 → 单端口启动

单端口推论(写代码前就要想清):应用要持久化数据、而代码由我写时,存储要放在应用内部 —— 文件型数据库、JSON 文件、浏览器侧存储都能发;一旦依赖外部数据库 / 缓存 / 队列,项目就发不出去。

§2 不可发布(遇到就说清,不要硬试)

类型 判据 交付路径
小程序 目录或其一级子目录含小程序工程文件(配置 json / 全局样式 / 带页面数组的配置) 回到小程序自己的工具链:开发者工具 → 填真实 AppID → 上传 → 提审 → 发布
Java / Maven / Gradle 沙箱无对应构建环境 自建环境
依赖外部中间件 声明了外部数据库 / 缓存 / 消息队列 先改成内置存储,或自建

§3 项目必须满足

  1. 服务监听环境变量里的端口,并绑定 0.0.0.0。
  2. 纯静态 / PDF 不需要手搓服务器 —— 工具会托管。
  3. Vite 系框架必须放行反代域名:否则请求会被拒成"该主机不被允许";把部署域名加入允许列表,或用等价的启动参数。

§4 一个目录里多个应用

  • 目录里有多个应用时,必须显式指定入口页,否则工具无法判断发哪个。
  • 单应用目录不必指定(自动识别)。
  • Node / Python / Go 项目的入口是 HTTP 根,不是某个文件。

§5 下线

  • 下线是破坏性动作:用户没明说就先确认。
  • 不得从目录推断下哪个应用:目录里有多个在线应用时工具会拒绝并要求选择 —— 列出来问用户,不要按名字相似或"最近发布的那个"自己挑(下线会掐断别人可能正在用的链接)。
  • 目录从未发布或已下线,如实说明,不要假装下线成功。

§6 本机实测坑

  1. 先落盘再发布:代码还在内存里就发,发出去的是空壳 —— 发布前确认文件已写到磁盘。
  2. 拿不到分享链接 ≠ 发布失败:建环境、上传、装依赖、启服务、生成链接都在工具内部完成;失败要如实报错,不要编一个链接。
  3. 构建命令不要猜:复杂仓库(多包 / 非常规构建)先停下问用户,不要自创构建命令。
  4. 对外只露分享链接:内部标识、原始地址都不该出现在回复里。
  5. 发布不等于通过验收:没跑 B 篇就发布,问题会在线上被别人先发现。

§7 能力边界与反例

能做:发布前置确认、可发布形态判定、单端口约束解释、多应用入口指定、下线流程、不支持类型的如实告知。

不做:

反例 正确去处
把小程序转成网页版部署 禁止;回到小程序工具链
没有本轮确认就发布 先确认
依赖外部数据库还要硬发 改成内置存储或自建
多个应用时替用户挑一个下线 列出来问用户
编一个分享链接糊弄交付 如实报错

常见误用与常见问题(FAQ)

常见误用(反模式)

误用 后果 正解
把输出接管道 必然失败,且失败得毫无线索 禁止把输出接管道
替换失败时猜匹配串 改错地方 先 grep 出实际行,不要猜匹配串(实测)
发布失败时编一个链接 欺骗 拿不到分享链接 ≠ 发布失败;失败要如实报错,不要编一个链接
沿用上一轮同意过的发布许可 越权外发 发布是外部动作,必须按本轮确认 —— 每次都看本轮用户是否明确要求;没要求就先做完再问
同一文件连发多个 Edit 而不回读 可能「报成功但没落盘」—— 产物里搜不到新关键字 = 没改上 改完源码必须回读确认;若故障集中在同一份页面、所有交互一起失效而其他页面全好 → 先怀疑源码没同步,不要先怀疑逻辑
用 dom.count(&#x27;.nav-item&#x27;) 查渲染后的模块 dom.count() 统计的是 DOM 字符串子串,不是 CSS 选择器 ⇒ 全部模块误报「骨架不完整」 写 dom.count(&#x27;class=\&quot;nav-item&#x27;)
从截图任意角落采样背景基准色 页面顶部满宽深色区块时取 a[0,0] 拿到的是区块色 ⇒ 之后全部误判 背景基准色必须从 HTML 的 :root/body 背景声明读取
中文文件名拼 [char]0xXXXX 编码坑(曾把「潘」写成「潜」,Edge 打开错误页,截图 65 KB 而正常 720 KB) 让 Node 把目标页复制到临时目录改名纯 ASCII,再让 Edge 开那个路径
截图异常小时直接归因逻辑 方向错 拿到异常小的图先怀疑打开的是错误路径
以为 --window-size 总生效 偶发不生效 ⇒ 视口退回默认小尺寸、走了窄屏分支 换算:innerWidth = --window-size 宽 − 24,innerHeight = − 92(1440,900 → 视口 1416×808)
设计稿类源页直接塞进 375px 框 手机套手机 见「手机外框 + 单屏切换器」;关键取舍:用「重设 srcdoc」而不是改 className
直接写死本地存储的用户信息 数据错 必须先 decode cookie 拿真实 accountId

常见问题

  • Q:任务落在哪一篇? A:先走「总路由:先判任务落在哪一篇」;五篇的边界见「五篇边界」。
  • Q:质量门在哪? A:见「Phase 6: Quality Gates」。
  • Q:设计基线有哪些? A:见「1.1 Baseline Configuration」「1.3 Design Rules」「1.4 Anti-Slop Techniques」「1.5 Forbidden Patterns」「1.7 Bento Paradigm」。
  • Q:发布这一步要注意什么? A:发布是外部动作,必须按本轮确认;建环境、上传、装依赖、启服务、生成链接都在工具内部完成。

附 · E 篇来源

本篇为 2026-09-19 吸收平台内置「发布为应用」系统件(版本目录结构)而新增,成为《万象-网页开发与验收》第五篇。只吸收能力类别与约束事实,不复制其文本;§6 本机实测坑、§7 反例表为自有增量。系统件仍负责实际的发布调用,本篇负责前置判断与纪律。

常见问题

5 问
答案由本站依据条目字段整理
万象-网页开发与验收怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_de2353f3/omni-web。安装方式为未标注。装完刷新技能列表或重开客户端。

万象-网页开发与验收支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

万象-网页开发与验收权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

万象-网页开发与验收收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

万象-网页开发与验收用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 未标注。