@readme/markdown 14.13.0 → 14.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -12645,6 +12645,10 @@ const tailwindPrefix = 'readme-tailwind';
12645
12645
 
12646
12646
 
12647
12647
  const TailwindRoot = ({ children, flow }) => {
12648
+ // `styles/gfm.scss` spaces the wrapped block children through this element and
12649
+ // relies on their margins collapsing through it. Keep it a plain block — no
12650
+ // border, padding, or block formatting context (overflow, display: flow-root/
12651
+ // flex/grid) — or the spacing breaks.
12648
12652
  const Tag = flow ? 'div' : 'span';
12649
12653
  return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement(Tag, { className: tailwindPrefix }, children);
12650
12654
  };
@@ -76301,6 +76305,15 @@ const HTML_TABLE_STRUCTURE_TAGS = new Set([
76301
76305
  'caption',
76302
76306
  'colgroup',
76303
76307
  ]);
76308
+ /**
76309
+ * Tags whose bodies a later mdxish transform keeps raw and never re-parses as
76310
+ * markdown. Both layers depend on this property, not on the tags being figures:
76311
+ * the transformer treats them as non-promotable, and the tokenizer must not claim
76312
+ * markdown islands inside them (a claimed island would never be re-parsed and would
76313
+ * leak as literal text). Currently just the figure tags, whose bodies are owned by
76314
+ * the figure reassembly transform (`mdxishJsxToMdast`).
76315
+ */
76316
+ const NON_REPARSED_BODY_TAGS = new Set(['figure', 'figcaption']);
76304
76317
  /**
76305
76318
  * The two HTML "foreign content" namespaces: SVG and MathML. Their descendants
76306
76319
  * (`<path>`, `<mrow>`, …) are namespaced XML, not HTML tags or components. A closed
@@ -99098,7 +99111,7 @@ function normalizeProperties(node) {
99098
99111
  * Identifies custom MDX components and recursively parses markdown children.
99099
99112
  * Replaces tagName with PascalCase component name for React component resolution.
99100
99113
  *
99101
- * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
99114
+ * @see .claude/context/MDXish/Processor Overview.md
99102
99115
  * @param {Options} options - Configuration options
99103
99116
  * @param {CustomComponents} options.components - Available custom components
99104
99117
  * @param {Function} options.processMarkdown - Function to process markdown content
@@ -101498,12 +101511,95 @@ function tokenizeMagicBlockText(effects, ok, nok) {
101498
101511
  */
101499
101512
 
101500
101513
 
101514
+ ;// ./lib/micromark/mdx-component/continuation-checks.ts
101515
+
101516
+
101517
+ /**
101518
+ * Partial constructs the `mdxComponent` tokenizer runs via `effects.check` to
101519
+ * decide whether the next line continues the block. They live here (not in
101520
+ * `syntax.ts`) because they hold none of `createTokenize`'s closure state — each
101521
+ * is a self-contained, single-line lookahead.
101522
+ */
101523
+ function continuation_checks_tokenizeNonLazyContinuationStart(effects, ok, nok) {
101524
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
101525
+ const self = this;
101526
+ return start;
101527
+ function start(code) {
101528
+ if (markdownLineEnding(code)) {
101529
+ effects.enter(types_types.lineEnding);
101530
+ effects.consume(code);
101531
+ effects.exit(types_types.lineEnding);
101532
+ return after;
101533
+ }
101534
+ return nok(code);
101535
+ }
101536
+ function after(code) {
101537
+ if (self.parser.lazy[self.now().line]) {
101538
+ return nok(code);
101539
+ }
101540
+ return ok(code);
101541
+ }
101542
+ }
101543
+ // A markup-only line opens with a tag (`<x`/`</x`) and ends (ignoring trailing
101544
+ // spaces) at a `>`. That distinguishes a structural continuation like
101545
+ // `<span>b</span></div>` from a paragraph like `<b>Note:</b> read *this*`.
101546
+ function tokenizeMarkupOnlyContinuation(effects, ok, nok) {
101547
+ let lastNonSpace = null;
101548
+ return start;
101549
+ function start(code) {
101550
+ // Caller guarantees we are at `<` at the (already de-indented) line start.
101551
+ effects.enter(types_types.data);
101552
+ effects.consume(code);
101553
+ return afterLessThan;
101554
+ }
101555
+ function afterLessThan(code) {
101556
+ if (code === codes.slash) {
101557
+ effects.consume(code);
101558
+ return afterSlash;
101559
+ }
101560
+ return afterSlash(code);
101561
+ }
101562
+ // The `<` (or `</`) must introduce a real tag, not a stray `<` in prose.
101563
+ function afterSlash(code) {
101564
+ if (asciiAlpha(code)) {
101565
+ lastNonSpace = code;
101566
+ effects.consume(code);
101567
+ return scanToLineEnd;
101568
+ }
101569
+ effects.exit(types_types.data);
101570
+ return nok(code);
101571
+ }
101572
+ function scanToLineEnd(code) {
101573
+ if (code === null || markdownLineEnding(code)) {
101574
+ effects.exit(types_types.data);
101575
+ return lastNonSpace === codes.greaterThan ? ok(code) : nok(code);
101576
+ }
101577
+ if (!markdownSpace(code))
101578
+ lastNonSpace = code;
101579
+ effects.consume(code);
101580
+ return scanToLineEnd;
101581
+ }
101582
+ }
101583
+ // A line ending whose next line isn't a lazy paragraph continuation. Checked so a
101584
+ // component body's blank/continuation lines aren't stolen by an interrupted paragraph.
101585
+ const continuation_checks_nonLazyContinuationStart = {
101586
+ tokenize: continuation_checks_tokenizeNonLazyContinuationStart,
101587
+ partial: true,
101588
+ };
101589
+ // Lookahead for `plainClaimLineStart`: is this line markup-only, or a paragraph
101590
+ // that merely starts with a tag? Run via `effects.check` so it never consumes.
101591
+ const markupOnlyContinuation = {
101592
+ tokenize: tokenizeMarkupOnlyContinuation,
101593
+ partial: true,
101594
+ };
101595
+
101501
101596
  ;// ./lib/micromark/mdx-component/syntax.ts
101502
101597
 
101503
101598
 
101504
101599
 
101505
101600
 
101506
101601
 
101602
+
101507
101603
  // Raw tags (type-1: pre/script/style/textarea) and block tags (type-6: div,
101508
101604
  // section, …) always start a block, so they stay flow even with trailing
101509
101605
  // content. Other lowercase tags (i, span, …) follow the type-7 rule and only
@@ -101513,16 +101609,19 @@ const htmlFlowTagNames = new Set([...htmlRawNames, ...htmlBlockNames]);
101513
101609
  // blank lines between nested JSX siblings don't fragment the block. Excludes table
101514
101610
  // tags (mdxishTables owns their blank lines) and voids (never close).
101515
101611
  const plainBlockClaimTagNames = new Set([...htmlBlockNames].filter(tag => !HTML_TABLE_STRUCTURE_TAGS.has(tag) && !HTML_VOID_ELEMENTS.has(tag)));
101516
- const mdx_component_syntax_nonLazyContinuationStart = {
101517
- tokenize: mdx_component_syntax_tokenizeNonLazyContinuationStart,
101518
- partial: true,
101519
- };
101520
- // Lookahead for `plainClaimLineStart`: is this line markup-only, or a paragraph
101521
- // that merely starts with a tag? Run via `effects.check` so it never consumes.
101522
- const markupOnlyContinuation = {
101523
- tokenize: tokenizeMarkupOnlyContinuation,
101524
- partial: true,
101525
- };
101612
+ const foreignContentTags = new Set(FOREIGN_CONTENT_TAGS);
101613
+ // Type-7 lowercase tags (a, span, button, and unknown names): CommonMark ends their
101614
+ // block at a blank line, fragmenting 4+ column children into indented code. Claimable
101615
+ // only in block-wrapper shape (see `blockWrapperOpenerRest`); lookalike openers
101616
+ // (`<https://…>`) never find a closer, so their claim retreats cleanly to CommonMark.
101617
+ // Voids never close and raw/foreign-content bodies have dedicated owners.
101618
+ const isBlockWrapperClaimTagName = (tag) => !htmlFlowTagNames.has(tag) && !HTML_VOID_ELEMENTS.has(tag) && !foreignContentTags.has(tag);
101619
+ // Both are 4 columns per CommonMark, but they mean different things: a tab advances
101620
+ // to the next multiple of TAB_STOP_WIDTH, and INDENTED_CODE_MIN_COLUMNS is the depth
101621
+ // at which a line would fragment into indented code. Named separately so the two
101622
+ // concepts don't read as one incidental literal.
101623
+ const TAB_STOP_WIDTH = 4;
101624
+ const INDENTED_CODE_MIN_COLUMNS = 4;
101526
101625
  function resolveToMdxComponent(events) {
101527
101626
  let index = events.length;
101528
101627
  while (index > 0) {
@@ -101581,6 +101680,23 @@ function createTokenize(mode) {
101581
101680
  // `plainClaimLineStart`: after a blank line it may only continue on a tag line.
101582
101681
  let isPlainBlockClaim = false;
101583
101682
  let pendingBlankLine = false;
101683
+ // Type-7 tag claimed pending the block-wrapper (opener alone on its line) check.
101684
+ let pendingBlockWrapperClaim = false;
101685
+ // True once a block-wrapper claim is confirmed; relaxes the top-of-body island gate
101686
+ // below (type-7 fallback is the fragmentation bug, not intentional indented code).
101687
+ let isBlockWrapperClaim = false;
101688
+ // Leading indent columns of the current plain-claim line, reset per line; ≥4 is
101689
+ // where CommonMark would fragment the island as indented code. Tabs advance to the
101690
+ // next 4-column stop — the same rule `expandIndentToColumns`
101691
+ // (processor/transform/mdxish/indentation.ts) applies, kept in sync by hand since
101692
+ // this side works on a `Code` stream, not a string. NB: do NOT swap this for
101693
+ // `self.now().column`; micromark bumps the point column by 1 per `horizontalTab`
101694
+ // code (the trailing `virtualSpace` codes don't move it), so it measures a tab as
101695
+ // 1 column, reviving the tab-under-measurement bug this math exists to avoid.
101696
+ let plainClaimIndentColumns = 0;
101697
+ // True once a non-blank line follows the opener: a deep island below it is nested
101698
+ // (cosmetic indent), not top-of-body indented code.
101699
+ let sawPlainBlockBodyContent = false;
101584
101700
  // Code span tracking
101585
101701
  let codeSpanOpenSize = 0;
101586
101702
  let codeSpanCloseSize = 0;
@@ -101814,16 +101930,11 @@ function createTokenize(mode) {
101814
101930
  }
101815
101931
  // End of opening tag
101816
101932
  if (code === codes.greaterThan) {
101817
- if (requiresBraceAttr && !sawBraceAttr) {
101818
- // Plain lowercase block tags stay claimable in flow, gated per line by
101819
- // `plainClaimLineStart`; everything else falls through to CommonMark.
101820
- if (!isFlow || !plainBlockClaimTagNames.has(tagName))
101821
- return nok(code);
101822
- isPlainBlockClaim = true;
101823
- }
101933
+ if (requiresBraceAttr && !sawBraceAttr && !claimBraceLessTag())
101934
+ return nok(code);
101824
101935
  effects.consume(code);
101825
101936
  onOpenerLine = isFlow;
101826
- return body;
101937
+ return pendingBlockWrapperClaim ? blockWrapperOpenerRest : body;
101827
101938
  }
101828
101939
  // Quoted attribute value
101829
101940
  if (code === codes.quotationMark || code === codes.apostrophe) {
@@ -101878,9 +101989,38 @@ function createTokenize(mode) {
101878
101989
  // `/ ` without `>` is just part of the attribute area
101879
101990
  return afterOpenTagName(code);
101880
101991
  }
101992
+ // Whether a brace-less lowercase flow tag is claimable: type-6 tags immediately,
101993
+ // type-7 tags pending the block-wrapper check; anything else is CommonMark's.
101994
+ function claimBraceLessTag() {
101995
+ if (!isFlow)
101996
+ return false;
101997
+ if (plainBlockClaimTagNames.has(tagName)) {
101998
+ isPlainBlockClaim = true;
101999
+ return true;
102000
+ }
102001
+ if (isBlockWrapperClaimTagName(tagName)) {
102002
+ pendingBlockWrapperClaim = true;
102003
+ return true;
102004
+ }
102005
+ return false;
102006
+ }
102007
+ // A type-7 tag is claimed only as a block wrapper: opener alone on its line
102008
+ // (trailing spaces ok). Inline content after the opener bails to CommonMark.
102009
+ function blockWrapperOpenerRest(code) {
102010
+ if (markdownSpace(code)) {
102011
+ effects.consume(code);
102012
+ return blockWrapperOpenerRest;
102013
+ }
102014
+ if (!markdownLineEnding(code))
102015
+ return nok(code);
102016
+ pendingBlockWrapperClaim = false;
102017
+ isPlainBlockClaim = true;
102018
+ isBlockWrapperClaim = true;
102019
+ return body(code);
102020
+ }
101881
102021
  // Continuation for multi-line opening tags
101882
102022
  function openTagContinuationStart(code) {
101883
- return effects.check(mdx_component_syntax_nonLazyContinuationStart, openTagContinuationNonLazy, continuationAfter)(code);
102023
+ return effects.check(continuation_checks_nonLazyContinuationStart, openTagContinuationNonLazy, continuationAfter)(code);
101884
102024
  }
101885
102025
  function openTagContinuationNonLazy(code) {
101886
102026
  sawLineEnding = true;
@@ -101925,6 +102065,9 @@ function createTokenize(mode) {
101925
102065
  }
101926
102066
  if (code !== codes.space && code !== codes.horizontalTab) {
101927
102067
  openerLineHasContent = true;
102068
+ // Continuation content marks a later deep island as nested, not indented code.
102069
+ if (!onOpenerLine)
102070
+ sawPlainBlockBodyContent = true;
101928
102071
  }
101929
102072
  if (code === codes.backslash) {
101930
102073
  effects.consume(code);
@@ -102005,7 +102148,7 @@ function createTokenize(mode) {
102005
102148
  return inFencedCode;
102006
102149
  }
102007
102150
  function fencedCodeContinuationStart(code) {
102008
- return effects.check(mdx_component_syntax_nonLazyContinuationStart, fencedCodeContinuationNonLazy, continuationAfter)(code);
102151
+ return effects.check(continuation_checks_nonLazyContinuationStart, fencedCodeContinuationNonLazy, continuationAfter)(code);
102009
102152
  }
102010
102153
  function fencedCodeContinuationNonLazy(code) {
102011
102154
  sawLineEnding = true;
@@ -102213,7 +102356,7 @@ function createTokenize(mode) {
102213
102356
  }
102214
102357
  // ── Body continuation (line endings) ───────────────────────────────────
102215
102358
  function bodyContinuationStart(code) {
102216
- return effects.check(mdx_component_syntax_nonLazyContinuationStart, bodyContinuationNonLazy, continuationAfter)(code);
102359
+ return effects.check(continuation_checks_nonLazyContinuationStart, bodyContinuationNonLazy, continuationAfter)(code);
102217
102360
  }
102218
102361
  function bodyContinuationNonLazy(code) {
102219
102362
  sawLineEnding = true;
@@ -102239,8 +102382,10 @@ function createTokenize(mode) {
102239
102382
  return inBodyBraceString(code);
102240
102383
  return inBodyBraceExpr(code);
102241
102384
  }
102242
- if (isPlainBlockClaim)
102385
+ if (isPlainBlockClaim) {
102386
+ plainClaimIndentColumns = 0;
102243
102387
  return plainClaimLineStart(code);
102388
+ }
102244
102389
  return bodyLineStart(code);
102245
102390
  }
102246
102391
  // Dispatch a non-blank continuation line: fenced code at line start, else body.
@@ -102268,8 +102413,16 @@ function createTokenize(mode) {
102268
102413
  // continue on a tag line (`<…`); any markdown island (`**bold**`, `[block:…]`, a
102269
102414
  // fence) refuses so CommonMark html-flow reparses it exactly as it does today.
102270
102415
  function plainClaimLineStart(code) {
102271
- // Leading whitespace only → treat as a blank line, matching CommonMark.
102272
- if (code === codes.space || code === codes.horizontalTab) {
102416
+ // Leading whitespace only → treat as a blank line, matching CommonMark. A tab
102417
+ // advances to the next stop; its trailing `virtualSpace` fillers add nothing
102418
+ // but must still be consumed or they'd read as line content below.
102419
+ if (markdownSpace(code)) {
102420
+ if (code === codes.horizontalTab) {
102421
+ plainClaimIndentColumns += TAB_STOP_WIDTH - (plainClaimIndentColumns % TAB_STOP_WIDTH);
102422
+ }
102423
+ else if (code === codes.space) {
102424
+ plainClaimIndentColumns += 1;
102425
+ }
102273
102426
  effects.consume(code);
102274
102427
  return plainClaimLineStart;
102275
102428
  }
@@ -102278,10 +102431,19 @@ function createTokenize(mode) {
102278
102431
  effects.exit('mdxComponentData');
102279
102432
  return bodyContinuationStart(code);
102280
102433
  }
102281
- // Across a blank line the block only continues onto a markup-only line; a
102282
- // paragraph that merely starts with a tag must fall back so its markdown
102283
- // parses and rehype-raw re-nests it into the wrapper.
102284
102434
  if (pendingBlankLine) {
102435
+ // A 4+ col island nested under other tags is cosmetic nesting indent, not code:
102436
+ // keep claiming so promotion dedents + re-parses it as markdown (RM-17560).
102437
+ // Block-wrapper claims extend this to top-of-body islands — their CommonMark
102438
+ // fallback is the fragmentation bug, not intentional indented code. Tags whose
102439
+ // bodies stay raw are excluded: a claimed island there would leak as literal text.
102440
+ if (plainClaimIndentColumns >= INDENTED_CODE_MIN_COLUMNS &&
102441
+ (sawPlainBlockBodyContent || isBlockWrapperClaim) &&
102442
+ !NON_REPARSED_BODY_TAGS.has(tagName)) {
102443
+ return plainClaimContinue(code);
102444
+ }
102445
+ // Otherwise only a markup-only tag line continues; markdown/prose falls back to
102446
+ // CommonMark so it parses and rehype-raw re-nests it into the wrapper.
102285
102447
  if (code !== codes.lessThan)
102286
102448
  return nok(code);
102287
102449
  return effects.check(markupOnlyContinuation, plainClaimContinue, nok)(code);
@@ -102302,66 +102464,6 @@ function createTokenize(mode) {
102302
102464
  }
102303
102465
  };
102304
102466
  }
102305
- function mdx_component_syntax_tokenizeNonLazyContinuationStart(effects, ok, nok) {
102306
- // eslint-disable-next-line @typescript-eslint/no-this-alias
102307
- const self = this;
102308
- return start;
102309
- function start(code) {
102310
- if (markdownLineEnding(code)) {
102311
- effects.enter(types_types.lineEnding);
102312
- effects.consume(code);
102313
- effects.exit(types_types.lineEnding);
102314
- return after;
102315
- }
102316
- return nok(code);
102317
- }
102318
- function after(code) {
102319
- if (self.parser.lazy[self.now().line]) {
102320
- return nok(code);
102321
- }
102322
- return ok(code);
102323
- }
102324
- }
102325
- // A markup-only line opens with a tag (`<x`/`</x`) and ends (ignoring trailing
102326
- // spaces) at a `>`. That distinguishes a structural continuation like
102327
- // `<span>b</span></div>` from a paragraph like `<b>Note:</b> read *this*`.
102328
- function tokenizeMarkupOnlyContinuation(effects, ok, nok) {
102329
- let lastNonSpace = null;
102330
- return start;
102331
- function start(code) {
102332
- // Caller guarantees we are at `<` at the (already de-indented) line start.
102333
- effects.enter(types_types.data);
102334
- effects.consume(code);
102335
- return afterLessThan;
102336
- }
102337
- function afterLessThan(code) {
102338
- if (code === codes.slash) {
102339
- effects.consume(code);
102340
- return afterSlash;
102341
- }
102342
- return afterSlash(code);
102343
- }
102344
- // The `<` (or `</`) must introduce a real tag, not a stray `<` in prose.
102345
- function afterSlash(code) {
102346
- if (asciiAlpha(code)) {
102347
- lastNonSpace = code;
102348
- effects.consume(code);
102349
- return scanToLineEnd;
102350
- }
102351
- effects.exit(types_types.data);
102352
- return nok(code);
102353
- }
102354
- function scanToLineEnd(code) {
102355
- if (code === null || markdownLineEnding(code)) {
102356
- effects.exit(types_types.data);
102357
- return lastNonSpace === codes.greaterThan ? ok(code) : nok(code);
102358
- }
102359
- if (!markdownSpace(code))
102360
- lastNonSpace = code;
102361
- effects.consume(code);
102362
- return scanToLineEnd;
102363
- }
102364
- }
102365
102467
  /**
102366
102468
  * Micromark extension that tokenizes MDX-like components.
102367
102469
  *
@@ -102415,6 +102517,7 @@ function mdxComponent() {
102415
102517
 
102416
102518
 
102417
102519
 
102520
+
102418
102521
 
102419
102522
  const buildInlineMdProcessor = (safeMode) => {
102420
102523
  // `jsxTable` must be present so a `<Table>`/`<table>` nested in a component
@@ -102486,7 +102589,7 @@ const toMdxJsxTextElement = (name, attributes, children, position) => ({
102486
102589
  // Raw-body tags (pre/script/style/textarea) must stay byte-exact; table
102487
102590
  // structure (`table` + `tableTags`) is owned by `mdxishTables` and figures by
102488
102591
  // `mdxishJsxToMdast`, both of which run later on raw html nodes.
102489
- const NON_PROMOTABLE_PLAIN_TAGS = new Set([...htmlRawNames, ...tableTags, 'table', 'figure', 'figcaption']);
102592
+ const NON_PROMOTABLE_PLAIN_TAGS = new Set([...htmlRawNames, ...tableTags, 'table', ...NON_REPARSED_BODY_TAGS]);
102490
102593
  const NESTED_TABLE_RE = /<table[\s>]/i;
102491
102594
  const isMarkdownPromotableHtmlTag = (tag) => !NON_PROMOTABLE_PLAIN_TAGS.has(tag);
102492
102595
  // Expression nodes count as plain so `<div>{1+1}</div>` keeps its current
@@ -102845,15 +102948,22 @@ function safeDeindent(text) {
102845
102948
  */
102846
102949
  const parseMdChildren = (value, safeMode) => {
102847
102950
  const parsed = getInlineMdProcessor({ safeMode }).parse(terminateHtmlFlowBlocks(safeDeindent(value).trim()));
102951
+ // Promote nested wrappers bottom-up so an outer wrapper sees markdown buried in a
102952
+ // child claimed whole (e.g. `<li>` in `<ol>`) before its containsMarkdownConstruct check (RM-17560).
102953
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- mutually recursive; hoisted decl, safe at runtime
102954
+ promoteComponentBlocks(parsed, safeMode, null);
102848
102955
  return parsed.children || [];
102849
102956
  };
102850
- // Parses trailing content into sibling nodes and re-queues the parent so any
102851
- // components among them get processed.
102852
- const parseSibling = (stack, parent, index, sibling, safeMode) => {
102957
+ // Splices trailing content in as sibling nodes. parseMdChildren has already
102958
+ // promoted any components nested among them (bottom-up); the main loop's
102959
+ // index-based walk then reaches these spliced siblings and the original children
102960
+ // they shift down, so no parent re-queue is needed. Each spliced subtree is marked
102961
+ // `promoted` so the walk doesn't redundantly re-descend into it (its html is gone).
102962
+ const parseSibling = (parent, index, sibling, safeMode, promoted) => {
102853
102963
  const siblingNodes = parseMdChildren(sibling, safeMode);
102854
102964
  if (siblingNodes.length > 0) {
102855
102965
  parent.children.splice(index + 1, 0, ...siblingNodes);
102856
- stack.push(parent);
102966
+ siblingNodes.forEach(siblingNode => promoted.add(siblingNode));
102857
102967
  }
102858
102968
  };
102859
102969
  // Ends the position at `consumedLength` so the component doesn't claim trailing
@@ -102919,16 +103029,19 @@ const substituteNodeWithMdxNode = (parent, index, mdxNode) => {
102919
103029
  * The opening tag, content, and closing tag are all captured in one HTML node
102920
103030
  * (guaranteed by the mdx-component tokenizer).
102921
103031
  */
102922
- const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
103032
+ function promoteComponentBlocks(tree, safeMode, source) {
102923
103033
  const stack = [tree];
102924
- const safeMode = !!opts.safeMode;
102925
- const source = file?.value ? String(file.value) : null;
102926
103034
  const parseOpts = { preserveExpressionsAsText: safeMode };
103035
+ // Subtrees a nested parseMdChildren already promoted wholesale (spliced siblings):
103036
+ // re-descending them finds no html to promote, so skip them.
103037
+ const promoted = new WeakSet();
102927
103038
  const processChildNode = (parent, index) => {
102928
103039
  const node = parent.children[index];
102929
103040
  if (!node)
102930
103041
  return;
102931
- if ('children' in node && Array.isArray(node.children)) {
103042
+ // Descend into container nodes (lists, blockquotes, …) so their html children
103043
+ // are reached — unless the subtree was already promoted upstream.
103044
+ if ('children' in node && Array.isArray(node.children) && !promoted.has(node)) {
102932
103045
  stack.push(node);
102933
103046
  }
102934
103047
  // Only html nodes can be an unparsed MDX component.
@@ -102983,7 +103096,7 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
102983
103096
  substituteNodeWithMdxNode(parent, index, componentNode);
102984
103097
  const remainingContent = contentAfterTag.trim();
102985
103098
  if (remainingContent) {
102986
- parseSibling(stack, parent, index, remainingContent, safeMode);
103099
+ parseSibling(parent, index, remainingContent, safeMode, promoted);
102987
103100
  }
102988
103101
  return;
102989
103102
  }
@@ -103010,43 +103123,57 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
103010
103123
  return;
103011
103124
  // Lowercase tags are usually inline; unwrap a sole paragraph so their
103012
103125
  // phrasing content isn't spuriously block-wrapped.
103126
+ let unwrappedSoleParagraph = false;
103013
103127
  if (!isPascal && parsedChildren.length === 1 && parsedChildren[0].type === 'paragraph') {
103014
103128
  parsedChildren = parsedChildren[0].children;
103129
+ unwrappedSoleParagraph = true;
103130
+ }
103131
+ // Without trailing content the whole node position is correct. With it, end
103132
+ // precisely at the closing tag — preferring source offsets when available (the
103133
+ // node's value strips blockquote/list prefixes), else the consumed span.
103134
+ let endPosition = node.position;
103135
+ if (contentAfterClose) {
103136
+ endPosition = source
103137
+ ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
103138
+ : positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd + closingTagIndex + closingTagStr.length);
103015
103139
  }
103016
103140
  const componentNode = createComponentNode({
103017
103141
  tag,
103018
103142
  attributes,
103019
103143
  children: parsedChildren,
103020
103144
  startPosition: node.position,
103021
- // With trailing content, end precisely at the closing tag. Prefer source
103022
- // offsets when available (the node's value strips blockquote/list
103023
- // prefixes); otherwise fall back to the whole node position.
103024
- endPosition: contentAfterClose
103025
- ? source
103026
- ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
103027
- : positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd + closingTagIndex + closingTagStr.length)
103028
- : node.position,
103145
+ endPosition,
103029
103146
  });
103030
103147
  substituteNodeWithMdxNode(parent, index, componentNode);
103031
- // Re-queue whichever side may hold further components.
103032
- if (contentAfterClose) {
103033
- parseSibling(stack, parent, index, contentAfterClose, safeMode);
103034
- }
103035
- else if (componentNode.children.length > 0) {
103148
+ // The unwrap reparented the children out of their paragraph, so re-walk them
103149
+ // since the children HTML may contain promotable syntax (e.g. `{…}`-attr tags)
103150
+ if (unwrappedSoleParagraph) {
103036
103151
  stack.push(componentNode);
103037
103152
  }
103153
+ // Trailing content after the close becomes siblings; parseMdChildren has
103154
+ // already promoted any components nested inside both sides, so the promoted
103155
+ // subtree itself needs no re-queue.
103156
+ if (contentAfterClose) {
103157
+ parseSibling(parent, index, contentAfterClose, safeMode, promoted);
103158
+ }
103038
103159
  }
103039
103160
  };
103040
- // Depth-first so nodes keep their source order.
103161
+ // Depth-first so nodes keep their source order. Index-based (not forEach) and
103162
+ // re-reading length each step: parseSibling splices siblings in mid-iteration, and
103163
+ // those — plus the original children they shift down — must all stay eligible.
103041
103164
  while (stack.length) {
103042
103165
  const parent = stack.pop();
103043
103166
  if (parent?.children) {
103044
- parent.children.forEach((_child, index) => {
103167
+ for (let index = 0; index < parent.children.length; index += 1) {
103045
103168
  processChildNode(parent, index);
103046
- });
103169
+ }
103047
103170
  }
103048
103171
  }
103049
103172
  return tree;
103173
+ }
103174
+ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
103175
+ const source = file?.value ? String(file.value) : null;
103176
+ return promoteComponentBlocks(tree, !!opts.safeMode, source);
103050
103177
  };
103051
103178
  /* harmony default export */ const mdx_blocks = (mdxishMdxComponentBlocks);
103052
103179
 
@@ -106800,7 +106927,7 @@ function mdxishMdastToMd(mdast) {
106800
106927
  * Processes markdown content with MDX syntax support and returns a HAST.
106801
106928
  * Detects and renders custom component tags from the components hash.
106802
106929
  *
106803
- * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
106930
+ * @see .claude/context/MDXish/Processor Overview.md
106804
106931
  */
106805
106932
  function mdxish(mdContent, opts = {}) {
106806
106933
  const { components: userComponents = {}, safeMode = false, variables } = opts;
@@ -107140,7 +107267,7 @@ function buildRMDXModule(content, headings, tocHast, opts) {
107140
107267
  * @param opts - The options for the render
107141
107268
  * @returns The React component
107142
107269
  *
107143
- * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
107270
+ * @see .claude/context/MDXish/Processor Overview.md
107144
107271
  */
107145
107272
  const renderMdxish = (tree, opts = {}) => {
107146
107273
  const { components: userComponents = {}, variables, ...contextOpts } = opts;