Skip to content

release 0.14.0 - #3

Merged
unDemian merged 2 commits into
mainfrom
release/0.14.0
Oct 9, 2026
Merged

unDemian merged 2 commits into
mainfrom
release/0.14.0

Conversation

@unDemian

@unDemian unDemian commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Note

Medium Risk
Touches the md4c wire format, span widening, and native Android/iOS hosts that every consumer compiles; release now gates on full app builds but still does not exercise runtime selection behavior on device.

Overview
Release 0.14.0 bumps the package and documents a large set of engine, native-host, and packaging fixes in CHANGELOG.md, with README and architecture docs brought in line.

Release gates: Adds native-build.yml and wires it into release.yml so publish waits on packing the tarball into a fresh React Native app and building Android (Gradle + library unit tests) and iOS (CocoaPods + xcodebuild). Local parity via npm run check:app:android / check:app:ios (scripts/build-test-app.sh). CI pins node-gyp@13.1.0; check-unreleased-breaking now rejects BREAKING notes on non-minor (pre-1.0) version numbers.

Android / iOS hosts: Fixes Kotlin compile failures in SelectableRunHostView.kt (undefined pressed, textClassifier under Kotlin 2). iOS skips redundant attributed-string work when Fabric republishes the same state handle.

Native parser & decoder: Vendors md4c patch 0001-fence-closed and exposes kDetailFenceClosed so fenced-code widening no longer mis-closes blocks or scans exponentially on long > lines. Link-tail widening follows link grammar (linear on pathological inputs); thematic breaks inside lists/blockquotes get real spans. Adds maxSourceLength (default 1 MiB UTF-16) enforced before parse. html: { allow } / extractLinks use iterative transforms to avoid stack overflow; decoder hardens bad event ranges and invalid UTF-8 in the string table. Smart quotes pair like cmark --smart.

Packaging: Podspec floor iOS 15.1; package.json exports ./dist/view/codeBlocks under the react-native condition; Node harness documented as shipped behind ./node.

Reviewed by Cursor Bugbot for commit 22d8d93. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Smart quote stack corrupts after pairing
    • Leave no longer restores quotes.length to the watermark after a transparent mark; it drops that frame's remaining delimiters and never grows the stack, so a later quote no longer reads undefined.

Create PR

Or push these changes by commenting:

@cursor push 642d3342f7
Preview (642d3342f7)
diff --git a/src/engine/native/__tests__/smart-punctuation.test.ts b/src/engine/native/__tests__/smart-punctuation.test.ts
--- a/src/engine/native/__tests__/smart-punctuation.test.ts
+++ b/src/engine/native/__tests__/smart-punctuation.test.ts
@@ -173,6 +173,36 @@
     expect((inlines(doc)[1] as { children: Inline[] }).children[0]).toMatchObject({ value: 'b” c' });
   });
 
+  test('pairing across a mark does not leave a hole for a later quote', () => {
+    // The inner closer splices the outer opener, so the stack is shorter than
+    // the mark's watermark. Restoring `quotes.length` to that watermark used
+    // to insert `undefined`; the next `"` then threw in `resolveQuote`.
+    const doc = parse('"hello *world" and* more"\n');
+    expect(inlines(doc)[0]).toMatchObject({ kind: 'text', value: '“hello ' });
+    expect((inlines(doc)[1] as { children: Inline[] }).children[0]).toMatchObject({
+      value: 'world” and',
+    });
+    expect(inlines(doc)[2]).toMatchObject({ kind: 'text', value: ' more”' });
+  });
+
+  test('text after a cross-mark pair still flushes', () => {
+    const doc = parse('"a *b" c* d\n');
+    expect(inlines(doc)[0]).toMatchObject({ kind: 'text', value: '“a ' });
+    expect((inlines(doc)[1] as { children: Inline[] }).children[0]).toMatchObject({ value: 'b” c' });
+    expect(inlines(doc)[2]).toMatchObject({ kind: 'text', value: ' d' });
+  });
+
+  test('an enclosed opener that shifted into the watermark cannot pair past the mark', () => {
+    // The `"` closer consumes the outer opener; the inner `'` slides into that
+    // slot. Truncating to the watermark would keep it and pair `baz'`.
+    const doc = parse('"hello *\'foo" bar* baz\'\n');
+    expect(inlines(doc)[0]).toMatchObject({ kind: 'text', value: '“hello ' });
+    expect((inlines(doc)[1] as { children: Inline[] }).children[0]).toMatchObject({
+      value: '’foo” bar',
+    });
+    expect(inlines(doc)[2]).toMatchObject({ kind: 'text', value: ' baz’' });
+  });
+
   test('an opener inside a resolved emphasis cannot pair past it', () => {
     // cmark drops every delimiter inside a resolved emphasis, so the `"` opened there is gone.
     const doc = parse('*a "b* c"\n');

diff --git a/src/engine/native/decode.ts b/src/engine/native/decode.ts
--- a/src/engine/native/decode.ts
+++ b/src/engine/native/decode.ts
@@ -198,7 +198,15 @@
         closeFrame(frame, stack[stack.length - 1], ctx);
         if (smart) {
           // Transparent emphasis too: cmark drops the delimiters a resolved emphasis encloses.
-          ctx.quotes.length = frame.quotesAtOpen;
+          // A closer inside the mark may have spliced an opener from below `quotesAtOpen`,
+          // so the stack can be shorter than that watermark. Never grow it back — that
+          // inserts a hole the next closer would read — and drop this frame's remaining
+          // delimiters even if they shifted into those reserved slots.
+          let w = 0;
+          for (let i = 0; i < ctx.quotes.length; i += 1) {
+            if (ctx.quotes[i].frame !== frame) ctx.quotes[w++] = ctx.quotes[i];
+          }
+          ctx.quotes.length = Math.min(w, frame.quotesAtOpen);
           ctx.quoteBottom = frame.quoteBottomAtOpen;
         }
         break;

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit a5682b0. Configure here.

Comment thread src/engine/native/decode.ts Resolved
@unDemian
unDemian merged commit 60144cd into main Oct 9, 2026
7 checks passed
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