Skip to content

fix: make hash persistence unicode-safe#367

Open
Pixel998 wants to merge 1 commit intomainfrom
fix/unicode-safe-hash-persistence
Open

fix: make hash persistence unicode-safe#367
Pixel998 wants to merge 1 commit intomainfrom
fix/unicode-safe-hash-persistence

Conversation

@Pixel998
Copy link
Copy Markdown
Contributor

Prerequisites checklist

AI acknowledgment

  • I did not use AI to generate this PR.
  • (If the above is not checked) I have reviewed the AI-generated content before submitting.

What is the purpose of this pull request?

This PR fixes explorer state persistence when the edited code contains Unicode characters. Previously, writing the shareable URL hash could throw InvalidCharacterError because the app used btoa(...) on a string containing characters outside the Latin1 range.

Steps to reproduce

  1. Replace the editor contents with code containing non-Latin-1 characters, for example:
const π = "😀";
  1. Open the browser console.
  2. Observe this error when the app tries to write the hash:
InvalidCharacterError: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

What changes did you make? (Give an overview)

  • Updated hash persistence to use a versioned UTF-8-safe format for newly generated links while preserving support for legacy links.
  • Added e2e coverage.

Related Issues

I ran into this while working on eslint/markdown#640, where I noticed that emojis in the editor were no longer present after a reload.

Is there anything you'd like reviewers to focus on?

@netlify
Copy link
Copy Markdown

netlify bot commented Mar 28, 2026

Deploy Preview for eslint-code-explorer ready!

Name Link
🔨 Latest commit ff40263
🔍 Latest deploy log https://app.netlify.com/projects/eslint-code-explorer/deploys/69c8455c1a9e6f0008e099d6
😎 Deploy Preview https://deploy-preview-367--eslint-code-explorer.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@eslint-github-bot eslint-github-bot bot added the bug Something isn't working label Mar 28, 2026
@eslintbot eslintbot added this to Triage Mar 28, 2026
@github-project-automation github-project-automation bot moved this to Needs Triage in Triage Mar 28, 2026
Copy link
Copy Markdown
Member

@amareshsm amareshsm left a comment

Choose a reason for hiding this comment

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

LGTM. I’ll keep it open for another day in case anyone else wants to review.

@amareshsm amareshsm added the accepted There is consensus among the team that this change meets the criteria for inclusion label Mar 29, 2026
@Pixel998 Pixel998 moved this from Needs Triage to Implementing in Triage Mar 30, 2026
@lumirlumir lumirlumir self-requested a review April 2, 2026 07:40
@lumirlumir lumirlumir moved this from Implementing to Second Review Needed in Triage Apr 2, 2026
@lumirlumir lumirlumir requested a review from Copilot April 2, 2026 08:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes explorer state persistence for Unicode-containing code by switching URL-hash serialization to a UTF-8-safe, versioned format while retaining backward compatibility with legacy hash links.

Changes:

  • Add v2: UTF-8-safe base64 encoding/decoding for persisted state stored in the URL hash, with legacy hash support preserved.
  • Harden hash reads by validating decoded payloads and falling back to localStorage when malformed.
  • Add Playwright e2e coverage for Unicode hash persistence, legacy link loading, and malformed-hash fallback.

Reviewed changes

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

File Description
src/hooks/use-explorer.ts Introduces versioned UTF-8-safe hash encoding/decoding and legacy decoding fallback to prevent btoa Unicode errors.
e2e-tests/persistence.test.ts Adds e2e tests covering Unicode-safe hash persistence, legacy hash compatibility, and malformed hash fallback behavior.

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

Comment on lines +120 to +121
const versionedHashPrefix = "v2:";

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Using versionedHashPrefix = "v2:" means URLSearchParams will percent-encode the colon in the hash value (and standard base64 will also trigger percent-encoding for +, /, =). This inflates shared URLs and reduces the maximum amount of state that can fit in the hash. Consider a URL-safe prefix/format (e.g., base64url) to avoid extra encoding overhead.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@lumirlumir lumirlumir Apr 2, 2026

Choose a reason for hiding this comment

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

Could using - instead of : be an option here, since it appears as - rather than %3A?

To provide additional context for other reviewers, the URL is shown below:

image

If it is suffixed with -, the URL is shown below:

image

Comment on lines +145 to +150
let binary = "";

for (const byte of bytes) {
binary += String.fromCharCode(byte);
}

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

encodeHashStorageValue builds a binary string via repeated binary += String.fromCharCode(byte) concatenation. This allocates/concats once per byte and can become expensive for larger persisted payloads (the editor state can grow quite large). Consider a chunked conversion strategy or a Uint8Array→base64 helper to reduce intermediate string churn.

Suggested change
let binary = "";
for (const byte of bytes) {
binary += String.fromCharCode(byte);
}
const chunkSize = 0x8000; // 32KB chunks to limit argument list size
const chunks: string[] = [];
for (let i = 0; i < bytes.length; i += chunkSize) {
const chunk = bytes.subarray(i, i + chunkSize);
chunks.push(String.fromCharCode(...chunk));
}
const binary = chunks.join("");

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I've tested this logic locally, but I'm not sure it really improves performance. Any further thoughts?

Copy link
Copy Markdown
Member

@lumirlumir lumirlumir left a comment

Choose a reason for hiding this comment

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

LGTM, thanks. I don't have any further comments, but the Copilot review is worth a look. I'll leave this open to get another opinion before merging.

Comment on lines +120 to +121
const versionedHashPrefix = "v2:";

Copy link
Copy Markdown
Member

@lumirlumir lumirlumir Apr 2, 2026

Choose a reason for hiding this comment

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

Could using - instead of : be an option here, since it appears as - rather than %3A?

To provide additional context for other reviewers, the URL is shown below:

image

If it is suffixed with -, the URL is shown below:

image

Comment on lines +145 to +150
let binary = "";

for (const byte of bytes) {
binary += String.fromCharCode(byte);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I've tested this logic locally, but I'm not sure it really improves performance. Any further thoughts?

@lumirlumir lumirlumir requested a review from a team April 2, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accepted There is consensus among the team that this change meets the criteria for inclusion bug Something isn't working

Projects

Status: Second Review Needed

Development

Successfully merging this pull request may close these issues.

5 participants