[Website] Accept encodeURIComponent-produced blueprint URL fragments - #3527
Merged
Conversation
… %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.
Contributor
There was a problem hiding this comment.
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
decodeBlueprintHashhelper (website + personal-wp) to decode hash fragments viadecodeURI, with adecodeURIComponentfallback only when needed. - Improve
parseBlueprinterror reporting by surfacing underlyingJSON.parsefailures 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.
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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 ashttps://playground.wordpress.net#encodedBlueprint, which resulted in an "Invalid blueprint" error in Playground with no debugging details.The
encodeURIComponentfunction used by Claude wasn't the right choice. Playground decodes those URLs viadecodeURI, notdecodeURIComponent. The former decodes%C4%85asąbut it leaves%2Fas%2F. The latter decodes%C4%85asą,%2Fas/,%3Aas:, and so on for,,/,&, and other codepoints that play a special role in URLs.Implementation
More precisely, this PR tries
decodeURIComponentfirst, and on failure it falls back todecodeURIto retain BC with existing Playground URLs that rely ondecodeURIsemantics.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
%XXescapes left over.Test plan
https://playground.wordpress.net/#%7B%22landingPage%22%3A%22%2Fwp-admin%2F%22%7Dboots into wp-admin%26inside a blueprint value preserves it