Skip to content

fix(arx): account for chat-safe transport when selecting ARX wire form - #15

Merged
baanish merged 1 commit into
cursor/arx-base64url-encoding-f81ffrom
codex/fix-base64url-candidate-selection-bug
Mar 20, 2026
Merged

baanish merged 1 commit into
cursor/arx-base64url-encoding-f81ffrom
codex/fix-base64url-candidate-selection-bug

Conversation

@baanish

@baanish baanish commented Mar 20, 2026

Copy link
Copy Markdown
Owner

Motivation

  • The auto-selection path for arx candidates never chose the new chat-safe base64url (B.) wire form because computeTransportLength treated all ASCII characters as equal length, letting punctuation-heavy base76 always win.
  • Selection should prefer the representation that is most likely to survive chat/link surfaces without percent-encoding, so transport scoring must model escape-prone ASCII punctuation conservatively.

Description

  • Update computeTransportLength in src/lib/payload/fragment.ts to conservatively count ASCII punctuation outside a chat-safe unreserved subset as escape-prone so those chars are treated like percent-escaped bytes instead of single characters.
  • Add the CHAT_SAFE_ASCII_FRAGMENT_CHARS regex and use it during transport-length computation so dense ASCII-safe encodings (like base64url B.) can win when appropriate.
  • Add a regression test in tests/arx-codec.test.ts that asserts encodeEnvelopeAsync(..., { codec: "arx" }) can emit the v1.arx.<dictVersion>.B. base64url fragment in auto mode.

Testing

  • Ran npm test -- --run tests/arx-codec.test.ts and the test file passed (all tests succeeded, including the new B. selection test).
  • Ran npm run typecheck and tsc completed without errors.
  • Ran npm run lint and the lint and public export docs checks passed.

Codex Task

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Mar 20, 2026

Copy link
Copy Markdown

Deploying agent-render with  Cloudflare Pages  Cloudflare Pages

Latest commit: 933dbf7
Status: ✅  Deploy successful!
Preview URL: https://6e559577.agent-render.pages.dev
Branch Preview URL: https://codex-fix-base64url-candidat.agent-render.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 88c236a4-d8f6-4d25-b889-94b505d5d721

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-base64url-candidate-selection-bug
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kilo-code-bot

kilo-code-bot Bot commented Mar 20, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

Analysis

The PR correctly implements chat-safe transport length calculation for ARX wire form selection:

  1. Regex Implementation (src/lib/payload/fragment.ts:39): The CHAT_SAFE_ASCII_FRAGMENT_CHARS regex properly defines the chat-safe character subset: alphanumeric plus URL-unreserved characters (-._~) plus = and #.

  2. Transport Length Calculation (src/lib/payload/fragment.ts:55): The logic correctly treats escape-prone ASCII punctuation as 3 characters (matching percent-encoded size), allowing the base64url (B.) form to win when base76 would grow after sharing through chat/link surfaces.

  3. Regression Test (tests/arx-codec.test.ts:299-318): The new test properly verifies that auto-selection can choose the base64url wire form for larger payloads.

The code handles edge cases correctly:

  • Uses codePointAt() to properly handle non-BMP characters (emojis, etc.)
  • Properly skips low surrogates when encountering 4-byte UTF-8 sequences
  • Regex has no global flag, avoiding lastIndex issues
Files Reviewed (2 files)
  • src/lib/payload/fragment.ts - Implementation change
  • tests/arx-codec.test.ts - Regression test

@baanish
baanish merged commit 612859e into cursor/arx-base64url-encoding-f81f Mar 20, 2026
4 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 933dbf7bc5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const cp = value.codePointAt(i)!;
if (cp < 128) {
len += 1;
len += CHAT_SAFE_ASCII_FRAGMENT_CHARS.test(value[i]) ? 1 : 3;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid preferring over-budget B. fragments over valid base76

This 3x penalty means encodeEnvelopeAsync(..., { codec: "arx" }) can now pick the chat-safe B. wire form even when it makes the real fragment exceed the 8,000-character limit that callers still enforce with hash.length - 1 (for example in createGeneratedArtifactLinkAsync). I reproduced this with a packed markdown payload of about 5,900 repeated bullet lines: the base76 fragment is 7,985 chars and would still work, but its computed transport length becomes 10,159 here, so the encoder instead selects an 8,331-char B. fragment and link creation rejects it. That is a user-visible regression for large payloads near the fragment budget.

Useful? React with 👍 / 👎.

baanish added a commit that referenced this pull request Aug 7, 2026
The old @git-diff-view/react output read as a debug dump with pseudo-table
headers; the decided direction (cursor session, criticism #15) was to hand
rendering to diffs.com's library. @pierre/diffs is Shiki-based, renders
into shadow DOM, and computes before/after diffs from raw contents, which
the old stack could not do (the oldContent/newContent path rendered an
empty diff body).

- diff-renderer.tsx: PatchDiff per parsed patch file and MultiFileDiff for
  before/after contents, imported through a small use-client bridge
  (src/lib/diff/pierre-react.ts) so the deferred chunk id stays stable
  across webpack graphs. The parse gate, per-file nav, binary handling,
  unified/split toggle, fallback, error boundary, and every data attribute
  are unchanged.
- The vendored stylesheet pipeline is gone: diff-view-stylesheet.ts,
  public/vendor/diff-view-pure.css(.br), the mirror step in
  assets:compress, its _headers entry, and the sync test. Shadow-DOM
  styles are themed via --diffs-* custom properties on
  .diff-renderer-frame, keeping diff bodies dark charcoal in both themes.
- Budget re-keyed to the artifact-stage dynamic import and tightened to
  160 KiB gzipped (measures 142 KiB; Shiki grammars load as separate
  on-demand chunks outside this key, unlike the old bundled highlighter
  at 323.9/340 KiB).
- Header tests keep exercising generic *.css.br serving via a neutral
  fixture name; the e2e stylesheet-lifecycle test now asserts shadow-DOM
  rendering with no external diff stylesheet.

Lint, typecheck, 321 unit tests, build budgets, and the chromium e2e
suite (28/28) pass. Visual snapshots are not regenerated here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
baanish added a commit that referenced this pull request Aug 7, 2026
The old @git-diff-view/react output read as a debug dump with pseudo-table
headers; the decided direction (cursor session, criticism #15) was to hand
rendering to diffs.com's library. @pierre/diffs is Shiki-based, renders
into shadow DOM, and computes before/after diffs from raw contents, which
the old stack could not do (the oldContent/newContent path rendered an
empty diff body).

- diff-renderer.tsx: PatchDiff per parsed patch file and MultiFileDiff for
  before/after contents, imported through a small use-client bridge
  (src/lib/diff/pierre-react.ts) so the deferred chunk id stays stable
  across webpack graphs. The parse gate, per-file nav, binary handling,
  unified/split toggle, fallback, error boundary, and every data attribute
  are unchanged.
- The vendored stylesheet pipeline is gone: diff-view-stylesheet.ts,
  public/vendor/diff-view-pure.css(.br), the mirror step in
  assets:compress, its _headers entry, and the sync test. Shadow-DOM
  styles are themed via --diffs-* custom properties on
  .diff-renderer-frame, keeping diff bodies dark charcoal in both themes.
- Budget re-keyed to the artifact-stage dynamic import and tightened to
  160 KiB gzipped (measures 142 KiB; Shiki grammars load as separate
  on-demand chunks outside this key, unlike the old bundled highlighter
  at 323.9/340 KiB).
- Header tests keep exercising generic *.css.br serving via a neutral
  fixture name; the e2e stylesheet-lifecycle test now asserts shadow-DOM
  rendering with no external diff stylesheet.

Lint, typecheck, 321 unit tests, build budgets, and the chromium e2e
suite (28/28) pass. Visual snapshots are not regenerated here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
baanish added a commit that referenced this pull request Aug 7, 2026
The old @git-diff-view/react output read as a debug dump with pseudo-table
headers; the decided direction (cursor session, criticism #15) was to hand
rendering to diffs.com's library. @pierre/diffs is Shiki-based, renders
into shadow DOM, and computes before/after diffs from raw contents, which
the old stack could not do (the oldContent/newContent path rendered an
empty diff body).

- diff-renderer.tsx: PatchDiff per parsed patch file and MultiFileDiff for
  before/after contents, imported through a small use-client bridge
  (src/lib/diff/pierre-react.ts) so the deferred chunk id stays stable
  across webpack graphs. The parse gate, per-file nav, binary handling,
  unified/split toggle, fallback, error boundary, and every data attribute
  are unchanged.
- The vendored stylesheet pipeline is gone: diff-view-stylesheet.ts,
  public/vendor/diff-view-pure.css(.br), the mirror step in
  assets:compress, its _headers entry, and the sync test. Shadow-DOM
  styles are themed via --diffs-* custom properties on
  .diff-renderer-frame, keeping diff bodies dark charcoal in both themes.
- Budget re-keyed to the artifact-stage dynamic import and tightened to
  160 KiB gzipped (measures 142 KiB; Shiki grammars load as separate
  on-demand chunks outside this key, unlike the old bundled highlighter
  at 323.9/340 KiB).
- Header tests keep exercising generic *.css.br serving via a neutral
  fixture name; the e2e stylesheet-lifecycle test now asserts shadow-DOM
  rendering with no external diff stylesheet.

Lint, typecheck, 321 unit tests, build budgets, and the chromium e2e
suite (28/28) pass. Visual snapshots are not regenerated here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant