本文へ移動
cccskills
無料GitHub で公開

screenshot-sync

Keep a plugin/demo repo's screenshots in sync with its UX. When editor / UI / design-token code changes in an opted-in repo (one carrying .pulp/screenshots.toml), re-capture and refresh every place each shot is consumed — README images, web-demo Open Graph (og.png) images, and gallery thumbnails — consistently across the WAM and WCLAP demo sites. Two capture routes: native headless (render_to_png / pulp-screenshot, Skia) for README / native-editor shots, and live-web headless Chrome (playwright-core) for WAM/WCLAP demo OG + gallery images. Backfills demos missing OG images. This is a PORTING / consistency tool — it re-shoots a repo's OWN UI, it never clones another product's art.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md19.8 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

screenshot-sync

Keep a repo's screenshots in lockstep with its UI. When the UX changes, the README shot, the web-demo Open Graph preview (og.png), and the gallery thumbnail all have to change with it — this skill re-captures them and the screenshot_sync_check.py gate fails a PR that forgot to.

Read the screenshot skill first for the raw capture mechanics (backends, the image-compositing trap, the content-floor oracle, vision-probe). This skill is the sync/consume orchestration on top of it.

When this applies

  • A repo carrying .pulp/screenshots.toml had a UX-path change and the screenshot-sync gate (pre-push / CI) or PostToolUse hint fired, OR
  • "update the screenshots / OG images / gallery thumbs", "backfill the WCLAP OG images", "the demo preview image is stale / links don't unfurl".

0. Confirm opt-in

Screenshots are a plugin-with-releases concern, not a core/tooling one. Check for .pulp/screenshots.toml at the repo root. Absent → the repo is not opted in; stop. Do not invent screenshots for a library/tool/core repo. Opt-in is per-repo by the file's presence — the same pattern ~/.config/pulp/daw-smoke.toml and .shipyard.local/config.toml use.

Default opt-in set: the packaged-demo and packaged-examples consumers (gpu-nam, bendr, superconvolver, spectral-lab, tempo-sampler, classic-effects, pulp-example-plugins). Everything else — pulp core included — stays off.

The manifest: .pulp/screenshots.toml

One file per opted-in repo, at its root. It lists the UX paths that should trigger a re-shoot and, for each screenshot target, its capture route and every place the image is consumed — so one re-shoot fans out to README + OG

  • gallery and they cannot drift.
schema_version = 1

# UX paths whose change should trigger a re-shoot (repo-relative, gitignore-style
# globs — the same dialect skill_path_map.json uses). A design-token change
# reskins every widget, so a trigger hit invalidates ALL targets in the repo.
[trigger]
paths = [
  "src/**/*_editor.cpp",
  "ui/**/*.js",
  "assets/design-system/**/*.css",
  "assets/design-system/**/*.tokens.json",
]

# ── Native README / editor shot (capture route A) ──
[[target]]
id      = "mono-synth-editor"
route   = "native"                 # render_to_png / pulp-screenshot, Skia/gpu
tool    = "pulp-mono-synth-shot"   # per-editor dumper target (brew_shots.cpp pattern)
size    = "720x460"
scale   = 2.0                      # match the native editor baseline (test_editors.cpp)
backend = "skia"                   # or "gpu" for requires_gpu_host() views
consumes = [
  { kind = "readme", path = "docs/img/mono-synth.png" },
]

# ── Web demo OG + gallery (capture route B) ──
[[target]]
id                  = "mono-synth-web"
route               = "web"
url                 = "example-plugins/mono-synth/index.html"
device_scale_factor = 2
consumes = [
  { kind = "og",      path = "example-plugins/mono-synth/og.png" },
  { kind = "gallery", path = "docs/gallery/mono-synth.png" },
  { kind = "og_meta", html = "example-plugins/mono-synth/index.html", rel = "./og.png" },
]

[[target]]
id                  = "example-gallery-web"
route               = "web"
url                 = "example-plugins/index.html"
device_scale_factor = 2
# A target may narrow the global trigger to just the paths that affect IT:
trigger = [ "ui/**/*.js", "assets/design-system/**" ]
consumes = [
  { kind = "og",      path = "example-plugins/og.png" },
  { kind = "og_meta", html = "example-plugins/index.html", rel = "./og.png" },
]

Schema:

  • schema_version — currently 1.
  • [trigger].paths — global UX globs; a match invalidates every target unless a target sets its own trigger list.
  • [[target]] — id (required, unique), route (native | web, required), optional trigger (per-target override), plus route-specific keys (tool/size/scale/backend for native; url/device_scale_factor for web) that document how to re-capture.
  • consumes[] — { kind, path } or { kind, html, rel }:
    • readme / gallery — the PNG a markdown/gallery references.
    • og — the PNG the page's og:image / twitter:image point at.
    • og_meta — the HTML head that must declare the OG/Twitter block pointing at that og.png (this is what backfills a demo missing OG images).

The gate verifies the readme / gallery / og PNG paths were refreshed when a trigger fired; og_meta is documentation for the head-template edit.

1. Identify stale targets

python3 tools/scripts/screenshot_sync_check.py --base origin/main --mode=report

It lists each [[target]] a diff invalidated. Read the hook output if it already fired. A token change lists every target.

2. Route A — native README / editor shots

For a plugin's native editor rendered to a PNG with no window. See the screenshot skill for backend selection; the essentials:

  • Prefer a per-editor dumper target (the pulp-bitches-brew repo's ui/brew_shots.cpp → brew-shots pattern; each plugin repo copies it as pulp-<name>-shot, named by the manifest tool =), or pulp-screenshot --demo for SDK demos.
  • Skia backend for asset-heavy UIs — the CoreGraphics canvas can't composite file images and renders filenames as placeholder text (the classic "broken import" that is really just the backend).
  • gpu backend (offscreen Dawn+Skia HeadlessSurface) for requires_gpu_host() views and any headless/CI/SSH capture — never the live WindowHost::capture_png() headless (it needs an interactive WindowServer session and hangs).
  • Match the native editor baseline scale 2.0 (test_editors.cpp in the example repos) so README shots align with the visual-regression baselines.
  • Assert ScreenshotStats::passes_content_floor — never commit a blank / near-blank PNG (a "file written" check misses the empty-frame bug).
  • Write each PNG to its consumes.kind = readme|gallery path.

pulp-screenshot is an Apple-only target; the per-editor dumper is the portable route and must be added to each plugin repo.

3. Route B — WAM / WCLAP web demo OG + gallery

The WAM/WCLAP plugin UI is a WASM build running in a browser, so the faithful capture is a real browser rendering the demo page — not native render_to_png. Pulp already ships this path (playwright-core against the system Chrome, no browser download); the OG generators reuse it.

Convention (match WAM exactly). WAM's OG image is a device-scale-2 element screenshot of the demo page itself, saved as a colocated og.png:

  • per-plugin page → screenshot the started editor panel (#panel) — ~1280×1234
  • gallery page → screenshot the card grid (.wrap) — ~1720×2002

The reference generators:

  • WAM (GitHub Pages, single gallery): web/gen-og-images.mjs (renders each page, presses the player's window.__start() seam, waits for window.__demo.started, screenshots the element at deviceScaleFactor: 2 with --mute-audio --autoplay-policy=no-user-gesture-required) + web/gen-og.mjs (injects the og:image/twitter:card=summary_large_image block only when a sibling og.png exists) — both in pulp-example-plugins / pulp-classic-effects.
  • WCLAP (Cloudflare Pages, combined /example-plugins/ + /classic-effects/ galleries): generated by examples/web-demos/wclap-build/cloudflare/assemble-gallery.mjs in pulp core. Its pluginPage() / galleryPage() emit the OG head block; the sibling gen-og-images.mjs renders each page's og.png. WCLAP pages need cross-origin isolation (COOP/COEP) to instantiate the threaded-wasm shared memory, so serve them with cloudflare/serve-headers.mjs (which applies the _headers rules), never a plain static server.

Steps:

  1. Build/assemble the demo Pages tree (assemble-gallery.mjs for WCLAP; the per-plugin .wasm modules are the only heavyweight input — they can be rebuilt with wasi-sdk or fetched from the live deploy).
  2. Serve it (WCLAP: serve-headers.mjs for COOP/COEP).
  3. Screenshot each page at deviceScaleFactor: 2, waiting on the ready predicate. Do NOT resume audio for its own sake — the demos are click-to-start; if a shot needs the started editor, drive the __start() seam under --mute-audio (see the local-dev audio etiquette in CLAUDE.md).
  4. Save as colocated og.png; assert it is non-blank before trusting it.
  5. Ensure the page head declares og:image / og:url / twitter:card / twitter:image → the colocated og.png (the og_meta consume). Edit the shared page template once, not each page by hand.

Landmine: on a PREVIEW deploy the og:image URL points at PRODUCTION, so the unfurl is stale

assemble-gallery.mjs stamps og:image / og:url from SITE_BASE, which DEFAULTS to the production origin (https://pulp-wclap-demos.pages.dev). Deploy to a preview alias (wrangler pages deploy --branch <name> → <name>.pulp-wclap-demos.pages.dev) and the page's og:image still points at PRODUCTION — so a crawler unfurling the PREVIEW link fetches the production og.png (the OLD image; your fresh capture sits on the preview alias and is never seen). Symptom: you regenerate og.png, confirm it live at <preview>/…/og.png, and the link preview STILL shows the old art — because the meta URL and the image are on different origins. Fix: rebuild the preview with the alias as base — SITE_BASE=https://<name>.pulp-wclap-demos.pages.dev node assemble-gallery.mjs — then redeploy; verify with curl -s <preview>/…/ | grep og:image. Production needs no override (default base is correct once merged). Separately, social caches (iMessage, Slack) hold the OLD unfurl until they re-crawl — changing the og:image URL forces a re-fetch, but a client may still show its cached card until the link is re-sent or its preview cache clears.

Landmine: assemble-gallery.mjs + wrangler deploy does NOT refresh og.png

og.png is content-hash cached — gen-og-images.mjs writes an og.png.key beside each image and SKIPS a page whose inputs (its own HTML + the assets it loads: wasm module, player, pulp-ui.js) are unchanged, re-shooting only pages whose hash moved. The trap: the normal deploy loop is assemble-gallery.mjs → wrangler pages deploy, and neither runs gen-og-images.mjs. So after you change an editor and redeploy, the share card keeps showing the OLD capture — a full-canvas editor shipped for a while with an og.png of the pre-editor knob grid. To refresh: run gen-og-images.mjs (or delete the page's og.png + og.png.key to force a re-shoot) BEFORE deploying. A full-canvas Skia editor only renders under the ANGLE/GPU Chrome flags, and the shot is a DEFAULT-state capture — it will not reflect interactive states (a knob moved, an engine toggled).

4. Consistency + backfill

  • Both galleries mirror the SAME example-plugin repos, so keep the OG head block and the og.png-generation step a shared template (WAM: gen-og.mjs's ogBlock(); WCLAP: assemble-gallery.mjs's page emitters) so WAM and WCLAP can't drift.
  • Backfilling a demo that lacks OG images = adding the og:image / og:url / twitter:card=summary_large_image / twitter:image lines to the page template + generating the colocated og.png. Social crawlers fetch the raw HTML and do NOT run JS, so the tags must be present at rest.

5. Verify + hand off

  • Re-run screenshot_sync_check.py --mode=report → clean.

  • Vision-probe before reading any capture for a visual judgment (screenshot skill).

  • Commit the PNGs + any head-template edit in the SAME PR as the UX change, or add a bypass trailer on the tip commit:

    Screenshot-Sync: skip target=<id|all> reason="..."
    

    Legitimate uses: a provably-invisible refactor, a docs-only touch that trips a glob, or a batch whose shots regenerate in a follow-up. Same philosophy as Skill-Update: skip / Version-Bump: skip.

The gate (three layers, mirrors skill-sync)

tools/scripts/screenshot_sync_check.py reads the repo's .pulp/screenshots.toml, diffs the change range against [trigger].paths, and checks each triggered target's consumed PNGs were refreshed (or bypassed). It DETECTS staleness; it does not capture (capturing is heavy + non-deterministic across machines — the re-shoot is the explicit step above). Wired exactly like the skill-sync gate:

LayerWhereMode
1 hintPostToolUse (hooks/scripts/cli-plugin-sync.sh)--mode=hint — advisory
2 report.githooks/pre-push--mode=report — enforcing (demote with PULP_DISABLE_PREPUSH_GATES=1)
3 CI.github/workflows/version-skill-check.yml--mode=report — authoritative, no env demotion

The check script ships from pulp core and is installed into consumer repos the same way the githooks are (tools/scripts/install-githooks.sh); the trigger map is repo-local (.pulp/screenshots.toml), because each plugin repo has its own layout.

Gotchas

  • A NEW page must be registered in gen-og-images.mjs by hand. Only the galleries auto-discover their demos (they read the plugins = [...] array back out of the assembled index.html). Every non-gallery page — the standalone pages, super-convolver, super-convolver-gpu — is a hardcoded entry. Add a page to assemble-gallery.mjs without adding it there and assemble-gallery.mjs still bakes an absolute og:image URL for it, pointing at a PNG that nobody ever generated. And Cloudflare serves a missing asset as its 404 page with HTTP 200, so this does not fail as a broken link — it fails as a blank unfurl, and a naive curl -w %{http_code} check says 200 and confirms the lie. That is the exact bug that shipped. check-og-images.mjs (file exists, real PNG magic bytes, aspect ≈ 1.91:1) and check-unfurl.mjs (fetch the LIVE URL as a crawler and follow the og:image) are the backstops — but the fix is to register the page. Doubly easy to miss when the page is emitted conditionally, like super-convolver-gpu, which only appears when its GPU DSP build tree exists.
  • Cache-bust the main-thread player entry ONLY — never the worklet processor or the DSP module. An AudioWorklet processor's registered name is derived from the URL it was added from, so the main thread and the worklet must agree on exactly one URL. A ?v= on either side forks the name and the node never constructs — the page loads, the UI paints, and there is simply no audio. The corollary bites in the other direction too: every module the ?v= IS stamped on must be an input to the hash (assemble-gallery.mjs's playerVersion() hashes the shell, widgets, both adapters, and the UI entry). Add a bustable module without adding it to the hash and its next change ships behind a stale cache entry — an OG shot regenerated against a page that is still serving the old bundle is the worst version of this, because it looks correct.
  • A page's UI module may be optional; a screenshot of the fallback is not a bug. The gallery assembler skips the both-ABI SuperConvolver section entirely when the WAM build tree (--wam-build) is absent, and builds pages with no custom UI when the wasm UI module (--ui-build) is absent — the player then renders its generated parameter grid. A browser with no WebGL2 context lands on that same grid at runtime. So before re-shooting a demo, confirm which UI the page actually mounted; otherwise you will capture the parameter grid, commit it as the plugin's og.png, and have "wrong" screenshots with a green build.
  • Wait for the player's SETTLE signal, not for the canvas element. mountPulpUi appends the canvas immediately and then fetches the wasm module, so "the canvas exists" is true a full second before anything is drawn on it. Waiting on it shoots the loading screen. The real signal is the loading class being gone (.pw-customui-loading). gen-og-images.mjs additionally refuses to shoot if that class is still present at timeout — a card is worse than no card, because a blank unfurl looks like a broken product and nothing in the build is red.
  • Check the og.png's PIXELS, not its existence. A loading-screen card is a well-formed PNG of the right size; only its content is wrong. check-og-images.mjs decodes it and requires ≥2% bright pixels in the control band — the live broken card measured 0.60%, every real one 4.2–7.0%. And note Cloudflare serves a missing asset as its 404 page with HTTP 200, so a fetch-and-check-status probe confirms the lie; you have to look at the bytes.
  • The shot is expensive; cache it on a content hash. Hash the page dir (plus the player bundle) into og.png.key and skip when it matches: 78 s → 2 s across a 30-page site when nothing moved. Verify the invalidation too — a cache that never busts silently ships yesterday's UI.
  • A brotli Content-Encoding on a plain-copied file breaks the page, not just the byte count. The GPU page's 10.4 MB .data was copied raw while _headers claimed brotli; the browser failed to decode it, the UI module never loaded, and the only symptom anyone saw was an OG card reading "Loading editor…". One shared copyUiFile() compresses every page's copy — don't hand-roll a second copy path.
  • CoreGraphics can't composite file images → filename-as-text placeholders; use the Skia backend for anything with assets.
  • Headless GPU → the offscreen gpu backend, not the live host (which hangs headless).
  • WCLAP needs cross-origin isolation (COOP/COEP) to instantiate; WAM does not — different serve step per route. Serve WCLAP with serve-headers.mjs.
  • Social crawlers don't run JS — OG tags must be static in the head at rest.
  • Demo-site HTML/OG templates for WAM live in the consumer repos (pulp-example-plugins / pulp-classic-effects web/gen-og*.mjs); the WCLAP combined-site template lives in pulp core (cloudflare/assemble-gallery.mjs).
  • This is a porting/consistency tool. It re-shoots the repo's own UI. Never fabricate or copy another product's screenshots.

Landmine: the OG bake is a TWO-PASS assemble — pass BOTH passes the same build trees

gen-og-images.mjs shoots each page, then the site assembler runs again to bake the og:image / twitter block into the HTML — a page gets its tags only when its og.png already exists on disk, which is only true on that second pass.

Run the second pass bare (no --wam-build / --ui-build / --gpu-build) and the assembler cannot find the build trees, so it skips every page that needs one — quietly, in a one-line note — and therefore never rewrites their HTML, which is the only thing that pass exists to do. /super-convolver-gpu/'s og.png was shot perfectly, then never referenced: the page shipped with no og:image at all and unfurled bare. The image existing on disk proves nothing; check the HTML.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

aax

無料

Optional AAX support for Pulp, including developer-supplied Avid SDK setup, CMake enablement, DigiShell/AAX Validator workflows, and local AAX builds on macOS or Windows.

日本語の概要は準備中です。原文の説明を表示しています。

Generous-Corp/pulp222026年10月10日 更新

Configure, implement, and test Pulp's optional desktop Ableton Link tempo-sync adapter while preserving the developer-supplied SDK, licensing, realtime, latency-compensation, and no-install boundaries.

日本語の概要は準備中です。原文の説明を表示しています。

Generous-Corp/pulp222026年10月10日 更新

Maintain Pulp's installed design-time agent capability manifest and public-surface ledger. Use when adding, removing, renaming, or materially changing public audio, MIDI, signal, timebase, or sequence APIs; registering a new algorithm for generators; changing capability support or deprecation state; or repairing agent-capabilities freshness, schema, fingerprint, tombstone, or installed-SDK tests.

日本語の概要は準備中です。原文の説明を表示しています。

Generous-Corp/pulp222026年10月10日 更新

android

無料

Android platform development for Pulp — NDK cross-compilation, Oboe audio, Dawn/Skia GPU rendering, JNI bridge, touch interaction, emulator workflows, and end-to-end smoke validation. Covers build, deploy, debug, and the gotchas discovered during bringup.

日本語の概要は準備中です。原文の説明を表示しています。

Generous-Corp/pulp222026年10月10日 更新

ara

無料

Optional ARA support for Pulp, including developer-supplied ARA SDK setup, CMake enablement, adapter companion APIs, validation, and ARA-aware plugin implementation guidance.

日本語の概要は準備中です。原文の説明を表示しています。

Generous-Corp/pulp222026年10月10日 更新

The measurement surface for ALL Pulp DSP and audio-pipeline work — read it BEFORE writing or gating DSP, not only when something already sounds wrong. Covers the C++ harness (signal generators, metrics, assertions, RenderScenario, contracts), the offline Audio Doctor (magnitude/frequency response, THD/THD+N, phase/group delay), and their Python sibling the Audio Quality Lab (tools/audio/quality-lab — null residual + alignment, LTAS log-spectral distance, spectral flux/centroid, HNR, Theil-Sen drift slope, Kaiser-sinc resampling, license-guarded corpus, regression-net ratchet). TRIGGER on AUTHORING work — "build/design an oscillator/filter/synth/effect", "add a DSP module", "what should the acceptance gate be", "how do I measure aliasing / anti-aliasing / alias floor", "null against a reference", "is this DSP correct", "choose a tolerance", "golden/regression corpus for audio", "measure drift or jitter", "A/B two renders" — AND on DEBUGGING work — "is there sound / no audio / I hear nothing", "does this filter/compressor/synth/delay produce the right signal", "prove the DSP / prove the contract", "measure the frequency response", "what's the THD / is it distorting", "what's the group delay / phase response / measured latency", "magnitude response curve", "render a test tone and assert", "audio regression", "64-frame works but 128 is silent", "sample-rate change pitch-shifted it", "describe what's in this buffer", "audio doctor", "compare before/after a DSP refactor". Reach for this BEFORE hand-rolling any FFT, null test, alias measurement, pitch tracker, or golden-render script — most of it already exists in one of the two lanes. Test/tool layer over HeadlessHost — deterministic, no audio device, no speakers. Off the realtime thread entirely.

日本語の概要は準備中です。原文の説明を表示しています。

Generous-Corp/pulp222026年10月10日 更新

Generous-Corp のスキルをすべて見る

このスキルの問題を報告する