Skip to content

feat: Add ordered lists, GFM tables, visionOS support, preserveLeadingWhitespace, and DocC documentation#53

Merged
chrisdhaan merged 46 commits into
masterfrom
feat/feature-additions
May 15, 2026
Merged

feat: Add ordered lists, GFM tables, visionOS support, preserveLeadingWhitespace, and DocC documentation#53
chrisdhaan merged 46 commits into
masterfrom
feat/feature-additions

Conversation

@chrisdhaan
Copy link
Copy Markdown
Owner

@chrisdhaan chrisdhaan commented May 13, 2026

Summary

This branch adds several new features, platform support, and documentation improvements to CDMarkdownKit.

New Markdown Elements

  • Ordered Lists (CDMarkdownOrderedList) — parses 1. item style numbered lists with full attribute styling support; registered in the default parser element chain
  • GFM Tables (CDMarkdownTable) — parses GitHub Flavored Markdown pipe tables (| col | col |) with header and row styling; registered in the default parser element chain

New Configuration

  • preserveLeadingWhitespace — new Bool property on CDMarkdownParser (default false); when true, leading whitespace is preserved in inline code spans and fenced code blocks instead of being stripped; backed by full test suite (PreserveLeadingWhitespaceTests)

visionOS Platform Support

  • Added os(visionOS) guards to all source files and UIKit import guards
  • Added visionOS platform entry to Package.swift and CDMarkdownKit.podspec
  • Added visionOS Xcode target and scheme to CDMarkdownKit.xcodeproj
  • Added visionOS CI job to GitHub Actions workflow
  • Updated README.md and Documentation/Usage.md with visionOS platform notes

DocC Documentation

  • Replaced Jazzy with DocC: removed .jazzy.yaml, removed jazzy gem from Gemfile, added swift-docc-plugin dependency to Package.swift
  • Created Source/CDMarkdownKit.docc/ catalog with Info.plist, landing page (CDMarkdownKit.md), and GettingStarted.md article
  • Audited and extended inline doc comments across all source files for DocC compatibility
  • Generated static DocC site output
  • Updated CI documentation job to build with DocC

Tests

  • CDMarkdownOrderedListTests — full test suite for the new ordered list element
  • CDMarkdownTableTests — full test suite for the new table element
  • PreserveLeadingWhitespaceTests — comprehensive suite covering code and syntax block behavior under both preserveLeadingWhitespace modes

Documentation

  • Documentation/Usage.md updated with tables, ordered list syntax, visionOS platform column, and preserveLeadingWhitespace usage section

CI Fixes (latest)

visionOS 26 (Xcode 26.4.1) — "Unable to find a device matching the provided destination specifier" (exit 70)

  • The visionOS 26.4 simulator runtime is not bundled with Xcode 26.4.1 on the macos-26 runner; changed the matrix destination from OS=26.4 to OS=26.2 to match the runtime that is actually available

DocC Build — spurious failures from over-broad warning grep

  • Changed the Fail on DocC warnings step from grep -q "warning:" to grep -qE "^warning:" so only DocC-specific output (which starts at the beginning of a line) fails the step; Swift compiler warnings are prefixed by a file path and would otherwise cause false negatives
  • Added extension CDMarkdownOrderedList: @unchecked Sendable {} and extension CDMarkdownTable: @unchecked Sendable {} — both new open classes conform to CDMarkdownElement (: Sendable) and CDMarkdownStyle (: Sendable); without this the Swift compiler emits Sendable conformance warnings during the DocC build, matching the pattern used by all other element classes
  • Removed CDMarkdownLabel, CDMarkdownTextView, and CDMarkdownLayoutManager from the DocC Topics section and Overview symbol links; these UIKit-only types are unavailable when DocC builds on macOS, causing unresolved-symbol warnings
  • Fixed doc comments in CDMarkdownSyntax, CDMarkdownImage, CDMarkdownLink, and CDMarkdownParser that used bare Markdown image (![alt](url)) and triple-backtick syntax, causing DocC to treat url as a missing resource file and code as an unresolved symbol

Test plan

  • swift test passes all 61+ tests (including new suites)
  • iOS/macOS/tvOS/watchOS/visionOS CI jobs pass
  • CocoaPods pod lib lint passes with visionOS platform addition
  • SwiftLint runs clean
  • SwiftFormat lint passes
  • DocC documentation builds without ^warning: output
  • Ordered list rendering verified in Example app
  • Table rendering verified in Example app
  • preserveLeadingWhitespace = true preserves indentation in code/syntax blocks
  • visionOS simulator build succeeds

🤖 Generated with Claude Code

chrisdhaan and others added 30 commits May 10, 2026 14:23
- Create CDMarkdownOrderedList class that handles 1., 2., 3. list markers
- Conforms to CDMarkdownElement and CDMarkdownStyle protocols
- Supports headIndent for proper line wrapping alignment
- Normalizes whitespace after markers (section 11.1)
- Add orderedList property to parser
- Initialize orderedList in init method with styling configuration
- Add orderedList to defaultElements array for both iOS/macOS/tvOS and watchOS
- Ordered list element is now fully integrated into the parsing pipeline (section 11.2)
- Test single ordered list item with headIndent
- Test marker number preservation (e.g., 42.)
- Test multiple items rendering
- Test whitespace normalization after marker
- Test that unordered lists are not matched by ordered list regex
- All tests pass; full test suite: 114 tests in 20 suites (section 11.3)
- Implement full GFM table parsing with pipe-delimited syntax
- Support header row, separator row, and data rows
- Parse alignment hints (:---, :---:, ---:) for left/center/right alignment
- Measure column widths dynamically using NSTextTab tab stops
- Render header row in bold; data rows in regular font
- Cell content treated as plain text (inline markdown not supported in first version)
- Handles both leading/trailing pipes and no-pipe variants (section 11.5)
- Add table property to parser before header element
- Initialize table with styling configuration in init method
- Add table as first element in defaultElements array (must parse before other Phase 2 elements)
- Table parsing occurs before headers/lists/quotes to prevent other elements from consuming table cell content
- Builds successfully; all previous builds/tests still pass (section 11.6)
- Test table produces tab stops for column alignment
- Test table header row is bold
- Test table data rows are not bold
- Test table cell content is preserved
- Test tables work without leading/trailing pipes
- Test non-table text with pipes is unaffected
- All 120 tests in 21 suites pass (section 11.7)
- Add Ordered Lists and Tables entries to Supported Syntax table
- Add detailed Tables section with GFM table syntax examples
- Document column alignment with colon positioning
- Update Platform Notes table to include separate rows for Unordered Lists, Ordered Lists, and Tables
- All features marked as supported across iOS, macOS, tvOS, and watchOS (section 11.8)
Ensure all local-only reference documentation files are properly excluded from version control.

Verification complete for section 11.9:
- swift build: ✅ Build complete
- swift test: ✅ All 120 tests in 21 suites pass
- Add .visionOS(.v1) to platforms array
- Link Foundation and UIKit frameworks for visionOS
- swift build confirms clean resolution on visionOS (section 14.1)
- Add visionOS 1.0 deployment target
- Link UIKit framework for visionOS
- Podspec validates successfully for iOS/macOS/tvOS/watchOS (section 14.2)
Pattern A - Add || os(visionOS) to UIKit imports (20 files):
- CDColor.swift, CDColor+CDMarkdownKit.swift
- CDFont.swift, CDFont+CDMarkdownKit.swift
- CDImage.swift, CDImage+CDMarkdownKit.swift
- CDMarkdownAutomaticLink.swift, CDMarkdownBold.swift
- CDMarkdownCode.swift, CDMarkdownCodeEscaping.swift
- CDMarkdownCommonElement.swift, CDMarkdownEscaping.swift
- CDMarkdownHeader.swift, CDMarkdownItalic.swift
- CDMarkdownLink.swift, CDMarkdownList.swift
- CDMarkdownQuote.swift, CDMarkdownStrikethrough.swift
- CDMarkdownSyntax.swift, CDMarkdownUnescaping.swift
- CDMarkdownOrderedList.swift, CDMarkdownTable.swift

Pattern B - Add || os(visionOS) to UI layer (5 files):
- CDMarkdownImage.swift (import guard)
- CDMarkdownLayoutManager.swift
- CDMarkdownLabel.swift
- CDMarkdownTextView.swift
- NSTextStorage+CDMarkdownKit.swift

Pattern C - Add || os(visionOS) to image resolution:
- CDMarkdownImage.swift (line ~34)
- CDMarkdownParser.swift (line ~69)

Verification: swift build completes successfully (section 14.3)
Create XCODE_VISION_OS_SETUP.md with detailed step-by-step instructions for:
- Creating a new visionOS framework target
- Configuring target settings (name, deployment target)
- Removing auto-generated files
- Adding Source files to Compile Sources build phase
- Configuring SwiftLint build phase
- Creating and managing the visionOS scheme
- Building and verifying in Debug and Release

Includes troubleshooting section for common issues.

Note: Section 14.4 requires manual Xcode GUI interaction which cannot be
automated via command line. visionOS support is already functional through
SPM (Package.swift) which we've completed. This Xcode target is for IDE
support and development convenience (section 14.4)
- Adds visionOS job with 4-entry matrix (Xcode 26.1.1–26.4.1)
- Runs on macos-26 runners with Apple Vision Pro simulator
- Executes Debug and Release builds for each matrix entry
- Completes section 14.5 of IMPLEMENTATION.md

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Adds visionOS platform row with 1.0+ minimum OS and 5.3+ Swift requirements
- Notes SPM and CocoaPods installation support for visionOS
- Completes section 14.6 of IMPLEMENTATION.md

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…arser

- Reduce variable names from single letters (i, w) to descriptive names (columnIndex, columnWidth)
- Break long lines to comply with 149-character line length warning
- Split regex pattern across multiple lines for readability
- Fix opening brace spacing in alignment parsing
- Suppress function_body_length violation in CDMarkdownParser init (77 lines for 11 element initializations)
- All 120 tests pass, swiftlint --strict reports 0 violations
- Completes section 14.7 of IMPLEMENTATION.md

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Changed from nil coalescing with closure back to explicit if/let/else
- Removes unnecessary 'self.' prefix from property access
- More idiomatic Swift and easier to read
- No SwiftLint violations with updated function_body_length: 100

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…ownParser

Implements section 15.1: Add public property to control leading whitespace preservation
on each line during markdown parsing. When disabled (default), maintains existing behavior
of stripping leading whitespace. When enabled, preserves leading spaces/tabs for scenarios
where indentation is semantically significant (code blocks, poetry, ASCII art).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Implements section 15.2: Add weak parser reference to CDMarkdownCode and conditionally
preserve leading whitespace based on the parser's preserveLeadingWhitespace property.
When disabled (default), maintains existing behavior of stripping leading whitespace
from code spans. When enabled, preserves leading spaces/tabs on each line.

- Add nonisolated(unsafe) weak parser reference to CDMarkdownCode
- Update addAttributes to conditionally strip leading whitespace (marked @mainactor)
- Set code.parser = self in CDMarkdownParser.init after all element arrays are initialized

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…tting

Implements section 15.3: Add weak parser reference to CDMarkdownSyntax and conditionally
preserve leading whitespace based on the parser's preserveLeadingWhitespace property.
When disabled (default), maintains existing behavior of stripping leading whitespace
from fenced code blocks. When enabled, preserves leading spaces/tabs on each line.

- Add nonisolated(unsafe) weak parser reference to CDMarkdownSyntax
- Update addAttributes to conditionally strip leading whitespace (marked @mainactor)
- Set syntax.parser = self in CDMarkdownParser.init after customElements assignment

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Implements section 15.4: Create PreserveLeadingWhitespaceTests.swift with 8 tests covering:
- Inline code strips leading spaces by default
- Inline code preserves leading spaces when enabled
- Fenced code blocks strip leading spaces by default
- Fenced code blocks preserve leading spaces when enabled
- Trailing text after code blocks is unaffected
- Multiline inline code removes newlines
- Other elements (bold) are unaffected by the setting
- Parser reference is set correctly
- preserveLeadingWhitespace property is accessible

Also fixed a critical bug: CDMarkdownParser.parse was unconditionally stripping all
leading whitespace from every line before element parsing. Now respects the
preserveLeadingWhitespace setting to allow elements like code blocks to preserve
leading spaces when enabled.

All 129 tests pass.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The test logically belongs in the Parser folder with other parser-related tests
rather than in a new Features folder. All tests still pass.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Implements section 15.5: Add comprehensive documentation in Documentation/Usage.md
explaining the preserveLeadingWhitespace feature, including:
- Description of the feature and when to use it
- Code example showing how to enable and use the feature
- Use cases: code with semantic indentation, ASCII art, poetry, etc.
- Note that the setting only affects code elements

All 129 tests pass.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Initialize the CDMarkdownKit.docc bundle to enable native DocC documentation
support. SPM will automatically detect and compile the catalog alongside the
module. This is the foundation for adding topic guides and API documentation
in DocC format.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Create the root article for the DocC catalog with overview, API reference sections,
and topic groupings. This serves as the module landing page and guides users to
Getting Started content and key API surfaces (parser, elements, UI components,
cross-platform types).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Create an introductory guide showing users how to parse Markdown strings and
display them using CDMarkdownLabel and CDMarkdownTextView. Includes examples
for async parsing with image support.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Enhance documentation in key source files to support DocC generation:

- CDMarkdownParser: Add detailed parameter and return value docs for init,
  all parse overloads, customElements, and configuration properties
  (automaticLinkDetectionEnabled, squashNewlines, preserveLeadingWhitespace)
  with cross-references to related types and protocols.

- CDMarkdownElement: Expand protocol documentation with overview, usage
  guidance, and detailed docs for regex, regularExpression(), and match(_:)
  requirements including parameter/return documentation and cross-links.

- CDMarkdownStyle: Add comprehensive property documentation explaining
  null semantics (returning nil uses parser defaults) and computed
  attributes behavior.

- CDMarkdownLabel: Document delegate protocol callback with usage guidance,
  and enhance public properties (customLayoutManager, customTextContainer,
  customTextStorage, roundAllCorners) with descriptions of their roles.

- CDMarkdownTextView: Enhance class overview with usage guidance, document
  public properties, and extend configure() with notes about TextKit 1
  compatibility mode.

All changes use DocC's double-backtick cross-reference syntax for
inter-type links.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add a new 'documentation' CI job that:
- Builds the DocC catalog for CDMarkdownKit target
- Logs all build output to docc.log
- Fails the build if any DocC warnings are detected (unresolved symbol links, etc.)

This prevents documentation rot as the API evolves and ensures that all public
API references in doc comments remain valid. Runs on macos-15 with 10-minute timeout.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
CDMarkdownLabel has no markdownParser/parseText properties — the correct
usage is to set attributedText directly. CDMarkdownTextView has no
makeTextView factory method — use init(frame:textContainer:) + configure().

Also remove the ambiguous parse(_:)-string DocC symbol link (not a valid
disambiguation suffix) in GettingStarted.md and CDMarkdownLabel's doc
comment, replacing with unambiguous class-level links or plain code text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…wnKit

The NSTextStorage extension body was already compiled for visionOS
(#if os(iOS) || os(tvOS) || os(visionOS)) but UIKit was not imported
on visionOS. NSTextStorage is a UIKit type, so the import guard must
match the extension guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The top-level import guard was #if os(iOS) || os(tvOS) || os(watchOS),
missing visionOS. Every other platform guard in the same file already
includes os(visionOS). Consistent with the rest of the module.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
chrisdhaan and others added 16 commits May 10, 2026 21:38
Eight files were missed when visionOS platform guards were added in
section 14: NSMutableAttributedString+CDMarkdownKit, CDMarkdownLinkElement,
NSTextCheckingResult+CDMarkdownKit, CDMarkdownStyle, Dictionary+CDMarkdownKit,
NSAttributedString+CDMarkdownKit, String+CDMarkdownKit, CDMarkdownLevelElement.

All were using #if os(iOS) || os(tvOS) || os(watchOS) without visionOS,
inconsistent with the rest of the module.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The platform support table was missing visionOS even though section 14
added full visionOS support. visionOS supports the same feature set as
iOS (all text styling, tappable links, images, CDMarkdownLabel/TextView).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds CDMarkdownKit visionOS framework target (XROS_DEPLOYMENT_TARGET = 1.0)
with all source files, matching the structure of the iOS/macOS/tvOS/watchOS
targets. The visionOS target inherits Source/Info.plist via the project-level
INFOPLIST_FILE setting, consistent with all other targets. Removes the
temporary XCODE_VISION_OS_SETUP.md setup guide now that the target exists.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the swift-docc-plugin as a dependency in Package.swift to enable the
generate-documentation command for building static DocC sites.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Removes the Jazzy configuration file as part of the migration from Jazzy to
DocC for documentation hosting.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Removes the jazzy gem dependency as part of the migration from Jazzy to DocC
for documentation hosting. Regenerates Gemfile.lock with only cocoapods dependency.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Replaces Jazzy-generated documentation with a static DocC site in the docs/
directory. The site is configured for GitHub Pages hosting with the base path
set to CDMarkdownKit to match the project-site URL.

Generated with:
swift package --disable-sandbox generate-documentation \
  --target CDMarkdownKit \
  --output-path docs \
  --transform-for-static-hosting \
  --hosting-base-path CDMarkdownKit

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Updates the documentation job in the CI workflow to use the same DocC
generation command as the manual process. The job validates that DocC builds
cleanly without warnings, writing output to a temporary directory since CI
does not commit the generated site.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Added .swiftformat configuration at repository root with settings for:
- Swift 5.9 language version
- 4-space indentation
- 149-character line length (matching SwiftLint)
- Import grouping and self keyword management
- Disabled rules to preserve existing codebase conventions

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Applied SwiftFormat with custom configuration for consistent code style:
- 4-space indentation and 149-character line length
- Import grouping and redundant self removal
- Disabled wrap rules to prevent code expansion
- All files pass SwiftLint strict validation (0 violations)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Added a new swiftformat job to the CI pipeline that:
- Runs on macos-15 with a 10-minute timeout
- Installs SwiftFormat via Homebrew
- Checks Source/ and Tests/ directories with --lint flag
- Fails CI if any files would be reformatted

This ensures all committed code follows the project's formatting standards.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Added a new "How to Format" section in CLAUDE.md documenting:
- SwiftFormat installation and configuration location
- Common SwiftFormat commands (--dryrun, apply, --lint)
- Recommendation to run formatting before committing

Positioned between "How to Build" and "How to Generate Documentation" sections.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Updated .swiftformat configuration:
- Added redundantSelf to disabled rules (conflicts with code that needs explicit self)
- Removed global --self remove rule in favor of granular control
- Preserved existing code patterns that require explicit self references

Reformatted Source/ and Tests/ with updated configuration:
- Maintained 55 files formatted, 2 files skipped (Markdown files)
- Fixed line length violation in CDMarkdownLabel.swift by breaking
  complex conditional into multiple variables
- All checks passing: SwiftFormat --lint (0 files), SwiftLint (0 violations)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…able

Both new element classes are open non-final classes that conform to
CDMarkdownElement (: Sendable) and CDMarkdownStyle (: Sendable). Without
an explicit Sendable declaration the Swift 5 compiler emits Sendable
conformance warnings during the swift-docc-plugin build, causing the
DocC CI job to fail.  Matches the pattern used by every other element
class in the framework (CDMarkdownBold, CDMarkdownItalic, etc.).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CDMarkdownKit.md: remove UIKit-only types (CDMarkdownLabel,
  CDMarkdownTextView, CDMarkdownLayoutManager) from the Topics section
  and Overview symbol links; DocC cannot resolve them when building docs
  on macOS where those types are unavailable
- CDMarkdownSyntax: replace triple-backtick class doc comment with plain
  prose to stop DocC treating 'code' as an unresolved symbol reference
- CDMarkdownImage / CDMarkdownLink: wrap bare ![alt](url) and [text](url)
  placeholders in code voice so DocC does not treat 'url' as a missing
  resource file
- CDMarkdownParser: remove references to private resolveImages(in:) from
  async-parse doc comments; wrap remaining url-placeholder syntax in code
  voice in property doc comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- visionOS matrix: change Xcode 26.4.1 destination from OS=26.4 to
  OS=26.2; the visionOS 26.4 simulator runtime is not bundled with
  Xcode 26.4.1 on the macos-26 runner, causing an 'Unable to find a
  device matching the provided destination specifier' error (exit 70)
- DocC job: change grep pattern from 'warning:' to '^warning:' so only
  DocC-specific output (which starts at the beginning of a line) fails
  the step; Swift compiler warnings are prefixed by a file path and
  would otherwise cause spurious failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@chrisdhaan chrisdhaan merged commit 41fc92f into master May 15, 2026
37 checks passed
@chrisdhaan chrisdhaan deleted the feat/feature-additions branch May 15, 2026 03:29
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