Add inline <adf> tag support#9
Open
merphx wants to merge 20 commits into
Open
Conversation
- Documents use cases, architecture, token shape, and affected files - Captures scope decisions (passthrough, array support, error parity) - Includes test plan covering happy path, errors, and non-regression Co-Authored-By: opencode <noreply@opencode.ai>
- Reorder tasks: tests first (Task 1), implementation second (Task 2) - Consolidate happy-path and error-case tests into a single task - Add AdfInlineToken type to eliminate 'as any' cast - Add code comments for extension purpose and renderer requirement - Use varied ADF node types (mention, emoji, date, status) in test fixtures - Add multiple-inline-tags-in-same-paragraph test case - Remove superseded non-regression test rather than replacing it Co-Authored-By: opencode <noreply@opencode.ai>
- Add inline array item missing-type error case - Add inline non-object/array JSON error case - Achieves parity with block-level error test suite Co-Authored-By: opencode <noreply@opencode.ai>
- Register a marked inline extension that captures <adf>…</adf> as a single adf_inline token before the default lexer can fragment it - Add AdfInlineToken type for safe casting in inlineToAdf - Handle adf_inline in inlineToAdf by delegating to parseAdfTag, keeping validation and error behaviour identical to block-level Co-Authored-By: opencode <noreply@opencode.ai>
- Remove test documenting that inline <adf> tags were unsupported - Four happy-path inline tests added in the prior commit fully cover this ground with stronger assertions Co-Authored-By: opencode <noreply@opencode.ai>
- Replace block-only caveat with note that both block and inline placement are supported - Add markdown examples showing inline usage in sentences and table cells Co-Authored-By: opencode <noreply@opencode.ai>
- Implementation is complete; design spec and plan served their purpose - Deleting to keep the repo clean post-implementation Co-Authored-By: opencode <noreply@opencode.ai>
- Use src.search(/<adf>/i) in start() to match the tokenizer regex, with a comment explaining the consistency rationale - Add JSDoc note on marked.use() warning about singleton mutation and its effect on consumers using marked for HTML rendering - Add matching note to README⚠️ section - Add block-level and inline tests for uppercase <ADF> tag handling Co-Authored-By: opencode <noreply@opencode.ai>
- Replace global marked.use() with a module-scoped 'new Marked()' so the adf_inline extension is registered on a private instance only - Importing marklassian no longer affects consumers using marked directly - Add regression test confirming marked.parse() is unaffected after import - Remove the README warning (issue is now resolved) Co-Authored-By: opencode <noreply@opencode.ai>
- Replace length > 0 and includes() checks with a precise t.is() against the exact HTML marked produces for a literal <adf> tag on a clean singleton - Makes the intent clear: a mutated singleton swallows content to '<p></p>\n', a clean one renders it as escaped literal HTML Co-Authored-By: opencode <noreply@opencode.ai>
- Collapse t.throws() call arguments to match biome's preferred style - Normalise single quotes to double quotes on string literal Co-Authored-By: opencode <noreply@opencode.ai>
- Handle adf_inline child tokens in em, strong, and del cases of inlineToAdf - Use flatMap with an adf_inline branch instead of map, so ADF nodes are emitted directly rather than passed through getSafeText (which drops them) - Add three failing tests covering inline ADF inside bold, italic, and mixed bold+inline content before fixing Co-Authored-By: opencode <noreply@opencode.ai>
- Extract resolveInlineToken(token, marks) helper before inlineToAdf - Replace duplicated adf_inline branches in em, strong, del cases - Replace standalone adf_inline case with single resolveInlineToken call - No behaviour change; all 37 tests pass Co-Authored-By: opencode <noreply@opencode.ai>
- Wrap long markdownToAdf call onto multiple lines Co-Authored-By: opencode <noreply@opencode.ai>
- Extend resolveInlineToken to recurse into em/strong/del children, merging each token's mark into an inherited accumulator - Make marks parameter optional (default []) — only supplied during recursion - Simplify em/strong/del cases in inlineToAdf to single resolveInlineToken calls - Add 6 tests covering nested bold/italic/strike combos and the intersection of nested emphasis with inline ADF nodes Co-Authored-By: opencode <noreply@opencode.ai>
- Relocate two intersection tests (nested emphasis containing inline ADF nodes) from core-markdown.test.ts to adf-passthrough.test.ts where they better reflect the feature under test - Retain the four pure nested-emphasis tests in core-markdown.test.ts as they cover cases not present in pre-existing fixture-based tests Co-Authored-By: opencode <noreply@opencode.ai>
- ADF does not permit non-text inline nodes (e.g. mention, date) to carry a link mark, so there is no valid ADF representation for this construct - Behaviour is undefined; document it explicitly under the adf tag caveats Co-Authored-By: opencode <noreply@opencode.ai>
- Add test showing block <adf> emits a top-level doc child while bare inline defaults to paragraph via marked context - Extend test with heading counter-example to clarify the paragraph comes from marked, not the handler Co-Authored-By: opencode <noreply@opencode.ai>
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.
Summary
Extends the existing
<adf>passthrough feature to support inline placement — inside paragraphs, table cells, headings, and anywhere elsemarkedprocesses inline content. Previously,<adf>tags only worked when surrounded by blank lines (block-level).I've done some real-world testing of this, and it's working quite well. I've also done a few static analysis passes with a coding agent, and I think all the use cases are covered.
Changes
Inline
<adf>tag support<adf>…</adf>can now appear within any content, embedding the ADF node(s) into the surrounding paragraph / list item / table cell / etc. content arrayparseAdfTagunder the hood:type, empty tag, non-object arrayitems
<adf>tag inside a Markdown link (e.g.[text <adf>...</adf>](https://example.com)) is not supported because it's not valid ADF (linkis a mark, not a node, so it can't act as a container for other ADF nodes). I wasn't really sure how the library should handle this, so I just threw my hands up and declared it unsupported 😅Reviewer notes
The inline extension uses marked's
level: 'inline'extension API to intercept<adf>…</adf>as a single token before marked's default inline lexer can split it into fragments. This is the only reliable approach — post-processing fragmented tokens would be brittle.The extension is registered on a private
new Marked()instance rather than viamarked.use()on the global singleton.marked.use()was the natural first approach for adding an extension, but it has a global side-effect: importingmarklassianwould silently register the extension in the consumer app which might not be desirable. A private instance avoids this entirely.When content appears by itself on a line without any formatting markers,
markedautomatically treats that as a paragraph node. This results in a subtle difference between howmarkdownToAdf()handles block-level<adf>nodes and inline<adf>nodes that appear by themselves. I think the behaviour as implemented is correct, but it may be a little bit unintuitive, so I've added a unit test explicitly documenting it (lib/test/adf-passthrough.test.ts:420):<adf>\n{...}\n</adf>) is added verbatim to the document.<adf>{...}</adf>) is stuffed into aparagraphnode (due to howmarkedworks).# <adf>{...}</adf>) does what you'd expect (e.g. puts the ADF inside aheadingnode).Compare this with how regular text is handled, and it might also make more sense (e.g.
Kia ora te ao!gets parsed as a paragraph vs# Kia ora te ao!is parsed as a heading — exactly the same behaviour if we replace the text with an inline<adf>node).