Skip to content

[Website] Accept encodeURIComponent-produced blueprint URL fragments - #3527

Merged
adamziel merged 5 commits into
trunkfrom
adamziel/blueprint-hash-decode-probe
Apr 25, 2026
Merged

[Website] Accept encodeURIComponent-produced blueprint URL fragments#3527
adamziel merged 5 commits into
trunkfrom
adamziel/blueprint-hash-decode-probe

Conversation

@adamziel

@adamziel adamziel commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

What

Decode Blueprints via encodeURIComponent.

Rationale

@annezazu asked Claude to set up a PR preview button. The model encodeURIComponent(JSON.stringify(blueprint)) to produce a Playground link such as https://playground.wordpress.net#encodedBlueprint, which resulted in an "Invalid blueprint" error in Playground with no debugging details.

The encodeURIComponent function used by Claude wasn't the right choice. Playground decodes those URLs via decodeURI, not decodeURIComponent. The former decodes %C4%85 as ą but it leaves %2F as %2F. The latter decodes %C4%85 as ą, %2F as /, %3A as :, and so on for ,, /, &, and other codepoints that play a special role in URLs.

Implementation

More precisely, this PR tries decodeURIComponent first, and on failure it falls back to decodeURI to retain BC with existing Playground URLs that rely on decodeURI semantics.

The error message is also more useful now — if parsing fails, you see the actual JSON error, plus a nudge about double-encoding when the input still has %XX escapes left over.

Test plan

  • CI green
  • Pasting https://playground.wordpress.net/#%7B%22landingPage%22%3A%22%2Fwp-admin%2F%22%7D boots into wp-admin
  • Existing encodeURI-built shareable links still load identically
  • A URL with a literal %26 inside a blueprint value preserves it
… %26

A user reported that Playground URLs built in a GitHub Actions script
with encodeURIComponent(JSON.stringify(blueprint)) silently failed with
a generic "Invalid blueprint" error. The root cause: the loader decoded
the fragment with decodeURI, which preserves reserved chars
(;/?:@&=+$,#) as their %XX form — JSON.parse then tripped on the
surviving %3A right after the first property name. encodeURI-produced
fragments (what the Playground UI itself generates) round-trip fine
with decodeURI, so this went unnoticed.

A straight swap to decodeURIComponent would fix the reported case but
regress a narrow-but-real edge case: hand-crafted URLs where the author
intentionally keeps %26 inside a blueprint string value so it reaches
the target API as a literal & encoded inside a single query parameter.
Under decodeURIComponent alone, %26 would decode to & and silently
change what the blueprint does.

So this takes the safer path: try decodeURI first (preserving every
URL that currently works, bit-for-bit), and only fall back to
decodeURIComponent when decodeURI produces something JSON.parse can't
accept. That covers the encodeURIComponent case without touching the
happy path.

Also made the error message honest. "Invalid blueprint" on its own told
the user nothing; the new message surfaces the JSON.parse error and, if
the input still contains %XX escapes after decoding, hints that the
URL may have been double-encoded.
@adamziel
adamziel requested review from a team, JanJakes and Copilot April 24, 2026 17:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR improves blueprint URL fragment decoding to accept fragments created with encodeURIComponent, while preserving legacy behavior for existing encodeURI-style fragments and intentionally preserved reserved characters.

Changes:

  • Add decodeBlueprintHash helper (website + personal-wp) to decode hash fragments via decodeURI, with a decodeURIComponent fallback only when needed.
  • Improve parseBlueprint error reporting by surfacing underlying JSON.parse failures and adding a double-encoding hint.
  • Add unit tests covering multiple encoding/edge scenarios for hash decoding and blueprint parsing.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/playground/website/src/lib/state/url/router.ts Improves blueprint parsing errors with underlying JSON parse info + hinting.
packages/playground/website/src/lib/state/url/router.spec.ts Adds tests for decodeBlueprintHash and improved parseBlueprint errors.
packages/playground/website/src/lib/state/url/resolve-blueprint-from-url.ts Introduces exported decodeBlueprintHash and uses it in URL resolution.
packages/playground/website/builder/builder.ts Updates builder hash-loading logic to use decodeURI-first + decodeURIComponent fallback.
packages/playground/personal-wp/src/lib/state/url/router.ts Mirrors improved parseBlueprint error reporting.
packages/playground/personal-wp/src/lib/state/url/resolve-blueprint-from-url.ts Adds local decodeBlueprintHash and uses it in URL resolution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/playground/website/src/lib/state/url/router.ts Outdated
Comment thread packages/playground/website/builder/builder.ts Outdated
Comment thread packages/playground/website/src/lib/state/url/resolve-blueprint-from-url.ts Outdated
Comment thread packages/playground/website/src/lib/state/url/router.spec.ts Outdated
Comment thread packages/playground/personal-wp/src/lib/state/url/resolve-blueprint-from-url.ts Outdated
Extracts decodeBlueprintHash into its own file with no side-effecting
imports — the previous spec was failing in CI with `location is not
defined` because importing it from resolve-blueprint-from-url.ts also
pulled in OpfsFilesystemBackend and friends. The new file lives next
to its twin and is re-exported from the original location so callers
don't notice.

Also tightens the helper to be safe against malformed %XX (decodeURI
itself can throw URIError, which the old code didn't catch), drops the
leading-# stripping into the helper so callers don't hand-roll it, and
adds a passing test for that case.

parseBlueprint now captures both the JSON.parse and base64 attempts so
the surfaced error matches the more likely intent (base64 error wins
when the input looks base64-shaped). The thrown message is built as
discrete sentences instead of with template-literal punctuation, so we
don't get awkward double-periods if the JSON error already ends with a
period.

builder.ts now reuses the shared decodeBlueprintHash, which removes
the open-coded inline copy and keeps semantics aligned across all
three call sites.

Spec uses btoa instead of Buffer for environment portability.
decodeBase64ToString uses window.atob (browser-only). The default vitest
environment for the website package is node, so the base64-path tests
were failing with "window is not defined" once the parseBlueprint
rewrite started exercising both fallback branches unconditionally.

Stubbing only what the helper actually reaches for, so we keep the
test surface honest — using Node's Buffer to back the polyfill.
Flips the order to match the rationale in the PR description:
encodeURIComponent is what external tooling reaches for, so the
decoder should reverse that path first. decodeURI stays as a fallback
to keep older Playground links that depend on its semantics working.
@adamziel adamziel changed the title Accept encodeURIComponent-produced blueprint URL fragments Apr 25, 2026
@adamziel
adamziel merged commit 26d46a0 into trunk Apr 25, 2026
47 checks passed
@adamziel
adamziel deleted the adamziel/blueprint-hash-decode-probe branch April 25, 2026 00:22
adamziel pushed a commit that referenced this pull request Apr 28, 2026
)

## Motivation for the change, related issues

Website now supports the `encodeURIComponent` feature included on PR
#3527. This pull request includes examples and updates the tutorials
with related content.

## Implementation details
Updated the following pages in English, Spanish, Portuguese and French:
- packages/docs/site/docs/blueprints/02-using-blueprints.md
-
packages/docs/site/docs/blueprints/09-troubleshoot-and-debug-blueprints.md
-
packages/docs/site/docs/blueprints/09-troubleshoot-and-debug-blueprints.md

## Testing Instructions (or ideally a Blueprint)
1. Pull the branch `update-docs-blueprint-url`
2. Install the packages 
3. Run the command `npm run dev:docs`
4. Review the content on the pages
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment