Skip to content

snowflake: smoother startup, browser-intent round-trip gate, and substrate un-weave#161

Draft
catalan-adobe wants to merge 20 commits into
mainfrom
worktree-fix-snowflake-feedback-improvements
Draft

snowflake: smoother startup, browser-intent round-trip gate, and substrate un-weave#161
catalan-adobe wants to merge 20 commits into
mainfrom
worktree-fix-snowflake-feedback-improvements

Conversation

@catalan-adobe
Copy link
Copy Markdown
Contributor

Summary

Improvements to the snowflake static-to-EDS skill, in five areas:

Startup (init / inputs / defaults)

  • Source URL is the only required input; target repo (from git), DA root (config default), conversion level (inferred), and slug/template (derived) are resolved automatically and shown in one confirmation summary that always displays before work begins.
  • Repo-level defaults (projectsDir, daRoot, branchPrefix, trunkBranch, tagPrefix) now live in a defaults block in MANIFEST.json and are stamped into .snowflake/config.json on install; user-edited values survive upgrades.
  • Knowledge files load just-in-time at the phase that needs them rather than all up front.

Phase 0 substrate install

  • A vanilla aem-boilerplate clone no longer triggers a confirmation pause. The installer reports which pre-existing files it replaces (all backed up) and proceeds; only genuine drift (a prior snowflake substrate that diverged) still requires --force.

Browser interactions

  • Phase 5 and the Quick Start express browser steps as intent ("open this URL", "evaluate this JavaScript", "take a screenshot") instead of hard-coded playwright-cli commands, so any host browser tool (cmux-browser, playwright-cli, …) can fulfil them.

Phase 5 round-trip health gate

  • The round-trip is now a pass/fail gate: the converted page must render (not blank), apply the overlay, match the source structure, be free of console/network errors, and pass the 1:1 DOM-equality check — on both local and production — before the run continues. The previous console-error check read window.__errors, which nothing populated; it is replaced with real capture plus an injectable fallback.

Substrate un-weave

  • The overlay engine moves out of scripts.js into a wholesale-replaced scripts/overlay-engine.js. scripts.js is no longer replaced — the installer injects a small import + a loadEager guard via a new anchored, idempotent inject operation (fail-loud with a manual snippet if the anchor is absent). This preserves Adobe's upstream scripts.js changes across installs. Substrate bumped to v1.1.0.

Test Plan

  • Installer unit tests: node --test plugins/aem/edge-delivery-services/skills/snowflake/scripts/install-substrate.test.mjs (6/6 pass)
  • npm run validate — snowflake skill validates
  • Real adobe/aem-boilerplate install: anchors match current upstream, injected scripts.js passes node --check, config stamped v1.1.0 + defaults, idempotent re-run no-ops
  • Manual: full Phase 5 browser health-gate e2e (DA auth + live conversion) before release

Note: includes design spec + implementation plan under docs/superpowers/ for the substrate un-weave.

🤖 Generated with Claude Code

catalan-adobe and others added 12 commits May 29, 2026 10:45
…on install

Adds a `defaults` object to MANIFEST.json with all five repo-level config
keys (projectsDir, daRoot, branchPrefix, trunkBranch, tagPrefix). The
installer now merges these into .snowflake/config.json using a three-layer
merge (defaults → existing config → substrateVersion/installedAt), so
user-edited values survive upgrades and a fresh install always has all keys
present. Bumps substrate to v1.0.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ase 1

Phase 0 now branches by dry-run outcome: clean installs proceed without a
separate file-list confirmation (the init summary covers it); drift and
custom-code-detected cases still surface details and pause for explicit input.

Phase 1 auto-detects the target repo via `gh repo view` / `git remote` and
reads daRoot from .snowflake/config.json instead of always asking the user.
Slug and templateName derivation is shown in the init summary rather than
triggering mid-phase follow-up questions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eter display

Source URL is now the only required input; repo, daRoot, level, slug, and
template name are all resolved automatically. A parameter summary is always
displayed before Phase 0 begins (no confirmation needed — the skill proceeds
immediately after showing it). DA token status is surfaced early and
non-blocking; phases 1-4 do not need it.

Reading order is now just-in-time: only SKILL.md + methodology.md load at
startup; the four heavy knowledge files are deferred to the phases that use
them (Analyze, Generate, Round-trip).

HOST-NOTES.md updated to accurately describe the defaults source-of-truth
(now actually in MANIFEST.json), document the three-layer merge, and add
`gh` (GitHub CLI) to the allowed-primitives list — it was already used in
Phase 5 but missing from the list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rowser instructions

The skill body no longer calls playwright-cli directly. Browser interactions
are now expressed as host-agnostic intents ("open this URL in a browser",
"evaluate this JavaScript", "take a screenshot") so the executing agent can
use whatever tool fits its environment — playwright-cli, cmux-browser, or
any other available primitive.

JavaScript payloads (what to evaluate and verify) are preserved unchanged;
only the tool invocation scaffolding is removed. HOST-NOTES.md updated to
map browser intents to tools per host and to list specific browser CLI calls
as a forbidden pattern in the skill body.

Also fixes the stale Quick start Phase 1 snippet that incorrectly showed
playwright-cli instead of curl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 5 is now an explicit pass/fail gate rather than an advisory report.
Before the run may continue to Phase 6, the converted page must pass six
checks on both local and production preview:

1. Renders (not blank) — visible text, rendered height, section count
2. Overlay applied — main[data-overlay] and body.appear
3. Structure matches decisions.json
4. No console errors (font-CORS tolerated, must be recorded)
5. No network failures or broken images (about:error / naturalWidth 0)
6. 1:1 with the source via dom-equality.mjs (PASS, or only the known
   wrapper-element deltas)

The previous consoleErrors check read window.__errors, which nothing ever
populates — it always returned 0. Replaced with real capture: the browser
tool's console/network logs, with an injectable in-page listener fallback
for tools that can't surface them. The evaluate payload now also reports
not-blank metrics and broken images.

dom-equality (check 6) is tightened from "FAIL with small deltas — move on"
to PASS-or-allowlisted-deltas-only. Results of all six checks are recorded
in state.json under healthGate so the pass is auditable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The installer's no-marker branch flagged every non-empty file that differed
from the bundled substrate as "custom content" and refused without --force.
But a vanilla aem-boilerplate clone always has non-empty stock files that
differ from the substrate — that's exactly what the skill replaces — so the
common case always tripped a redundant confirmation pause.

The no-marker case now installs directly: it reports which pre-existing files
it replaces (so a genuinely custom file is still surfaced) but does not block.
Originals are backed up unconditionally and the init summary already disclosed
the file count, so the install is reversible and pre-disclosed.

The drift case (marker present but files diverged from the bundled version)
still refuses without --force — there a prior snowflake substrate could carry
intentional customization, so a human decision is warranted.

Phase 0 docs collapse the former Clean/Custom-code cases into one no-pause
Fresh-install case; SKILL.md Initialization note updated to match. Also folds
a duplicate "After install" heading introduced in an earlier edit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o-op

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s on overlay DOM

loadSections(element) queries div.section descendants. The overlay template
contains the original static page's <main> markup with plain design elements,
not EDS wrapper div.section nodes, so the call finds zero matches and returns
immediately. The old woven scripts.js guard was defensive but unnecessary; the
new hook-based approach omits it intentionally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 29, 2026

Tessl Skill Lint

edge-delivery-services — clean


✅ All 1 tile(s) clean.

Updated by tessl-lint for commit 0e71d50.

@catalan-adobe catalan-adobe marked this pull request as draft May 29, 2026 11:15
Page-level overlay is the safer, more common path. Making it the default
means /snowflake <url> proceeds directly to page-level conversion without
a feasibility-gate question. level=auto is still available for users who
want the analysis to decide.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of hardcoding /marketing, the DA root now defaults to the current
git branch name — the same branch the skill uses for code. This matches
the common EDS convention where DA content lives under a path named after
the branch. The config daRoot key still overrides when set; the value is
always shown in the init summary for correction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ck fires

The installer stamped "daRoot": "" into config, which the resolution logic
treated as "set" — so the branch-name fallback never triggered. Removing
daRoot from defaults entirely means the key is absent in a fresh config,
and the Phase 1 resolution correctly falls back to the current branch name.
Users who explicitly set daRoot in their config are unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ibility

The slot-identification rules in Phase 2 and methodology.md said "Visible
text" which caused the agent to skip content inside hidden tab panels,
collapsed accordions, and inactive carousel slides. CSS visibility is
irrelevant during content extraction — the template's own JS/CSS handles
show/hide at render time. All DOM content is now treated as authorable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Large pages with many sections or slottable elements risk silent content
loss during page-level conversion (context pressure, repetitive-structure
fatigue). Block-level processes one section at a time and is immune.

Phase 2 now checks: >8 sections or >100 slottable elements. When the gate
fires and level=page was the default (not explicitly requested), it
auto-switches to block-level and proceeds. When level=page was explicitly
passed, it warns but respects the user's choice. level=auto/block/check
are unaffected. The outcome is recorded in decisions.json as
complexityGate + sectionCount + slottableElementCount.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…per)

When an <a> contains authorable text alongside decorative non-authorable
children (inline SVGs, icon images), placing data-slot on the <a> causes
writeSlot to destroy the decorative content at runtime.

Adds:
- Learnings entry documenting the pattern and the general rule
- Phase 2 mixed-content detection: slots flagged with mixedContent=true
  in decisions.json when <a> contains SVG/decorative-img/icon children
- Phase 3 span-wrapper rule: wrap only the authorable text in a
  <span data-slot>, leaving decorative siblings as template chrome
- Self-check #7: DOM-based post-generate validation that no data-slot
  element contains SVG or decorative image descendants

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Block-level conversion generates N independent blocks — own JS, CSS,
content model, no shared state. Phase 3 (B.5) now explicitly states
blocks can be generated in parallel if the host supports concurrent
work dispatch. The hint is intent-level (no specific tool prescribed),
matching the browser-intent pattern.

HOST-NOTES updated: replaced the "out of scope in v1" parallelism
disclaimers with per-host guidance — Slicc dispatches one scoop per
block via the cone, Claude Code dispatches one Agent subagent per block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deliberately high for now to avoid premature auto-switching while we
gather data on real pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant