Skip to content

DEV: Add the documentation site (markbridge.dev)#54

Draft
gschlager wants to merge 75 commits into
mainfrom
docs
Draft

DEV: Add the documentation site (markbridge.dev)#54
gschlager wants to merge 75 commits into
mainfrom
docs

Conversation

@gschlager

Copy link
Copy Markdown
Member

This adds a real documentation site for Markbridge, built with Astro Starlight
and deployed to markbridge.dev. So far the only docs were the README and a few
loose Markdown files — this gives the gem a proper home with guides, format
references, and the architecture background.

I built it around the main use case (migrating a forum into Discourse), but kept
the plain "convert markup to Markdown" path as the first thing people see, so it
doesn't come across as a migration-only tool.

What's in it:

  • Getting started, plus a guide per input format (BBCode, HTML, MediaWiki,
    TextFormatter).
  • Migrating to Discourse — the overview and the placeholder pattern: an AST
    node + parser handler + renderer Tag, then reading the nodes back off
    conversion.ast afterwards.
  • Customizing the renderer and Extending Markbridge for custom tags and
    handlers.
  • Concepts — architecture, the AST, parsers, renderers, performance.
  • Reference — the upgrade guide (now the canonical one; the root
    UPGRADING.md just points here), the changelog (generated from GitHub
    Releases, with #NNN turned into links), and a link to the API docs on
    rubydoc.info.

A few things worth pointing out:

  • The code blocks are tested. spec/docs runs every ```ruby block in
    the site, so the examples can't drift from the API. Snippets that can't run
    (old-API "before" examples, bare method signatures) are fenced as ```rb
    still highlighted, just not executed.
  • Diagrams are Excalidraw (sources + light/dark SVGs) with click-to-zoom.
  • I dropped the old examples/ directory. The tested docs snippets replace it,
    and the standalone files had already drifted from the current API.
  • CI: the docs build runs on every PR (build only); deploy to Pages happens
    on merge to main. Pages is set up for Actions deploy with the markbridge.dev
    custom domain.

I also folded in one unrelated fix: the JRuby CI job was failing on
bundle install because a Dependabot bump pulled rbs (a native extension)
onto JRuby through mutant's dependency chain. I guarded mutant to MRI only, the
same way commonmarker already is.

gschlager added 30 commits July 7, 2026 00:51
Replaces the flat-file docs under docs/ with an Astro + Starlight site
that will publish to markbridge.dev via GitHub Pages.

Site content covers the same ground as the legacy docs but restructured
into a Getting Started page, per-format guides (BBCode, HTML, MediaWiki,
TextFormatter), an Extending guide, a Configuration guide, and Concept
pages for architecture, the AST, parsers, renderers, and performance.

Tooling:
- bin/docs wrapper runs pnpm scripts against docs/ (defaults to dev).
- .silo.yml installs nodejs+pnpm from dnf, runs pnpm install on setup
  and sync, exposes port 4321, and autostarts a docs daemon.
- .github/workflows/docs.yml deploys to GitHub Pages on pushes to main
  that touch docs/ or the workflow itself.

Branding assets (icon and social card masters) live under docs/branding/;
the site-served copies are in docs/public/ and docs/src/assets/.
Use the standard foreground color for the Starlight site title so the
brand-orange accent stays on links and highlights. Mention TruffleRuby
and JRuby alongside CRuby in the requirements, landing page, and
performance guide.
Running the docs locally creates a top-level /.pnpm-store/ cache;
keep it out of git.
Tighten the hero tagline (formats are listed in the first card now,
not enumerated twice), drop the Seti file-tree icons in favour of
Starlight's built-in line-art set for visual consistency, and give
each feature card the rotation accent on its border plus a 2px
hover lift. The lift composes with the CardGrid stagger offset so
right-column cards no longer leap when hovered.
Drop sections that duplicate the docs site or are derivable from the
code (architecture overview, AST tree, module structure, constants
table, file/extension-point tables, generic RSpec advice). Keep the
project-specific rules an agent can't infer: workflow scripts, style
conventions RuboCop doesn't enforce, mutation-testing guardrails, and
the MarkdownEscaper hot-path warning.
The mutation-coverage refactor (8ddfa6b) changed
elements_from_current(MAX_AUTO_CLOSE_DEPTH) to
MAX_AUTO_CLOSE_DEPTH - 1, lowering the actual peek-ahead bound by one.
The docs still advertised "looks ahead up to 5 tokens" and listed a
separate MAX_PEEK_AHEAD constant that doesn't exist. Reframe in terms
of the real bound (sequences of up to 5 mismatched closing tags) and
fix the constant location in the performance table from
ClosingStrategies::Base to ClosingStrategies::TagReconciler.
Each format guide now demonstrates the umbrella loader for the
format it covers, and the getting-started "lighter alternative"
hint enumerates them so readers see HTML/TextFormatter pull
Nokogiri while BBCode/MediaWiki don't.
Match the HTML guide's wording so readers see the gemfile snippet
and the "only for this parser" clarification, instead of the
one-line note that easily reads past.
Drop the misleading "only" from the Nokogiri requirement note in the
HTML and TextFormatter guides — Nokogiri is needed by both, so neither
guide owns it exclusively. On the landing page, restate the same dep
generically ("for some parsers") and replace the BBCode-specific
"single-pass scanner" wording with "single-pass parsing" so the
Performance-conscious card describes all four parsers, not just BBCode.
Switch single-format snippets to per-format umbrella requires and
drop the `-rmarkbridge/all` preload from the YJIT CLI example. The
landing page's "See it in action" snippet now requires `bbcode` and
`html` directly to match the two methods it calls; getting-started's
first conversion uses `markbridge/bbcode`, with the explanatory
paragraph re-led to introduce the format-specific path first and
mention `markbridge/all` as the "all four" shortcut. Only one
`markbridge/all` reference remains, which is the intentional one in
that paragraph.
Add docs/scripts/generate-changelog.mjs, a small Node script that hits
the GitHub Releases API and writes docs/src/content/docs/changelog.md.
Hook it into pnpm dev/start/build so the page is always populated, and
expose it as `pnpm changelog` for ad-hoc runs. The generated file is
gitignored — the GitHub release page is the single source of truth.

Each release is rendered compactly: H3 heading with version and date,
H4 category headings (Features / Bug Fixes), with the redundant
"What's Changed" wrapper and category emojis stripped. The wrapper
stays on the GitHub release page itself, where the body has no H1 to
anchor under.

Replace the external "Releases" sidebar entry with an internal
Changelog page, and extend .github/workflows/docs.yml to fire on
release: published / edited / deleted so editing a release on GitHub
republishes the site within a minute or two.
Add spec/docs/ — a new spec tier for cross-checks between code and
the docs site — and put the first guard in it: a bidirectional check
that every Markbridge::AST::* class appears in the AST concepts page,
and that the page doesn't reference classes that no longer exist.

The check caught seven classes the page had silently lost track of
(Email, Event, MarkdownText, Mention, Paragraph, Poll, Upload), so
expand the node-hierarchy tree to cover them. Split the existing
"Content" group into "Block-level" and "Content" so paragraph- and
heading-style nodes have a natural home alongside the new Email and
Discourse-specific leaves.

Note the new tier in AGENTS.md and add a matching exclusion to
.rubocop.yml so RSpec/DescribeClass doesn't fire on tier-meta specs.
Walks docs/src/content/docs/**/*.{md,mdx}, extracts every ```ruby
fenced block, and runs each in a subprocess so blocks don't pollute
each other. Setup injected once per block:

  - If the block already has a require "markbridge/..." line, leave
    it alone — only `include Markbridge` is prepended.
  - Otherwise prepend `require "markbridge/all"; include Markbridge`,
    so the example pulls in the full library and short-form refs
    like `AST::Foo` resolve without the `Markbridge::` prefix that
    docs typically omit for readability.

Failures surface stderr, stdout, and the wrapped source so the
specific docs file:line is easy to find. No skip mechanism yet —
add one if it turns out we need an escape hatch for snippets that
genuinely can't run.
Two HTML/MDX comment directives now drive how each ```ruby block runs:

  <!-- spec:continue -->     prepend every preceding ```ruby block in
                             this file. Use for narrative docs that
                             build up an example across snippets.

  <!-- spec:before
    ruby = "code"            invisible setup for the next block. Use
  -->                        when readers need to see a placeholder
                             (e.g. an `input` variable) but the spec
                             needs it filled in.

Multiple `spec:before` comments accumulate; both directives flush at
the next ```ruby block. There is intentionally no skip directive — the
goal is for every Ruby fenced block in the docs to run.

Fix: extract_blocks looped indefinitely on single-line HTML comments
because read_comment returned `chunk.length - 1` (= 0) when `<!-- ... -->`
fit on one line, so the parser cursor never advanced past it. Returning
`chunk.length` resolves the hang.

The spec now runs each subprocess with `RUBYOPT="-W0 -rbundler/setup"`
so snippets can `require` Gemfile gems. Add `csv` and `benchmark` to
the Gemfile — they're stdlib gems on older Rubies and bundled gems on
4.0+, and several docs examples idiomatically use them.
Annotate every previously-failing ```ruby block with the right
directive so the docs code-example spec runs all 41 of them green
(and finds none of the 13 that used to fail at first run):

  - spec:continue on the second-and-later blocks of narrative chains
    in extending.md, bbcode.md, and textformatter.md, so each block
    sees the AST/handler/tag classes defined in earlier blocks.
  - spec:before for invisible setup before snippets that show
    placeholder identifiers (`input`, `my_registry`, `MyBoldTag`,
    `MyCalloutHandler`) or stub I/O (`CSV.foreach`, `File.readlines`).
  - The `...` ellipsis in parsers.md's lambda example was a literal
    syntax error; replace with a minimal real body (`{ parent }`).

Fix one real doc bug uncovered by the spec: bbcode.md showed
`ClosingStrategies::Strict.new` (zero args), but the strategy now
requires a TagReconciler. Replace with the correct construction
through `HandlerRegistry.build_from_default`.
Captures the user journey we keep seeing in real importers (resolve IDs,
emit placeholders, collect side data, surface unknown tags) plus the
concrete pain points the current API doesn't smooth over: default-handler
fallback boilerplate, mutable trackers threaded into Tag constructors, the
three-file placeholder triad, and the lack of a high-level unknown-tags
surface.

Sketches four API additions (result object, side-data emission, overlay
registration, optional placeholder DSL) and the docs reorganization that
follows once the API stabilizes — explicitly NOT a commitment, just a
parking place so we don't lose context while the actual work happens on a
fresh branch off main.
Pivot the docs site to the task-oriented "I'm migrating a forum to
Discourse" perspective and bring every page in sync with the migration-
API redesign coming in PR #30.

New top-level "Migrating to Discourse" section with overview,
placeholders, and a full walkthrough that ports examples/forum_migration.rb
end-to-end. The placeholders page makes explicit what was implicit:
Tag return strings are spliced verbatim — only AST::Text goes through
the escaper — so placeholder sigils reach the output untouched.

The old guides/configuration.md is gone; renderer-side knobs move to
a new customization/customizing-renderer.md describing the
Markbridge.discourse_renderer(...) factory. PR30's UPGRADING.md is
ported under reference/upgrading.md.

Existing pages updated for: Conversion return type, removed
Markbridge.configure global, MediaWiki handlers: kwarg rename,
TextFormatter handler processor: arg, interface.emit and html_mode?
on the rendering interface table, the build-once / reuse-many
renderer pattern.

MIGRATION_API_PLAN.md updated to reflect §1-3 implemented in PR30,
§4 (DSL) considered and rejected, and the resolved open questions.

Every code sample in the migration section was verified against PR30's
branch via a sibling worktree before commit.
Add a third dependabot ecosystem entry covering docs/'s npm/pnpm
package tree (Astro, Starlight, sharp). Grouped weekly so the docs
deps don't leak into the gem's normal DEPS PRs.
Three pipeline diagrams ship as native Excalidraw SVG exports with
light and dark variants, embedded via <figure class="diagram">
markup that toggles between the two via Starlight's [data-theme]:

  - concepts/architecture: three-box parse → AST → render pipeline
  - migrating/overview: five-stage pipeline with the
    interface.emit side channel
  - migrating/placeholders: the placeholder triad showing how a
    source tag splits into a placeholder string and a side-data
    emission

Each diagram has three files in docs/public/diagrams/:

  - <name>.excalidraw — editable JSON source (drop into
    excalidraw.com to tweak)
  - <name>.svg — light-mode export
  - <name>-dark.svg — dark-mode export (toggle Excalidraw's
    "Dark mode" checkbox in the export dialog)

Visual language: amber for boundaries (input, output), blue for
processes (Parse, Render), purple for data (AST::Document, custom
AST nodes), green for output, red dashed for side channels
(interface.emit, emissions).

Card surface in custom.css uses a soft tint that adapts to the
page theme — #f8fafc in light mode, #1e2230 in dark mode — so the
diagram has a frame without becoming a bright white island in dark
mode.
Re-introduce the dialog-based click-to-zoom that worked for the
mermaid era, scoped to figure.diagram > img. Native <dialog>
opened with showModal() lands in the top layer, immune to
ancestor CSS transforms — Starlight's view-transition wrapper
otherwise breaks position: fixed and the zoom never lifts off
the article column.

Clicks delegate from document; closes on backdrop click or Escape.
The clone of the visible <img> (light or dark per current theme)
is appended to the dialog content, so the inline copy stays in
place when the dialog closes.
Two Vite tweaks that together fix the "edit an SVG, hard-refresh,
still see the old one" loop:

  - `server.headers: { 'Cache-Control': 'no-store' }` — Vite's
    default static-file middleware lets the browser cache `public/`
    assets aggressively. With no-store the browser must revalidate
    every fetch, so changed files actually appear on refresh.
  - `server.watch.usePolling: true` — `inotify` doesn't fire on
    every filesystem (overlayfs, networked mounts, some containers).
    Polling guarantees file changes are picked up regardless of the
    underlying FS. Interval: 200ms.

Build mode is unaffected (Astro emits hashed asset URLs there, so
caching is fine).
Swap the made-up `[upload=42]filename.jpg[/upload]` for phpBB3's
real attachment syntax, `[attachment=0]filename.jpg[/attachment]`.
The `0` is the *position* of the attachment within the post (zero-
indexed, the same shape vBulletin and IPB use under different tag
names) — not an upload ID, since the file metadata lives in the
source forum's attachments table joined to the post. That position-
to-row indirection is exactly the kind of resolution
`interface.emit` is built for: capture the position at render time,
let the importer match it against the source database after the
fact.

Renames track the new shape:
  - UploadPlaceholder → AttachmentPlaceholder
  - source_id → position
  - emissions key :uploads → :attachments
  - placeholder string [upload|N] → [attachment|N]

Handler now overrides Markbridge's built-in [attachment]/[attach]
defaults rather than registering an unused [upload] alias. Wired
with a single shared handler instance per the multi-alias caveat.

Verified end-to-end against PR30's gem branch including the
[attach] alias path. Diagram source updated to match; the
exported SVGs need a re-export from Excalidraw to pick up the
new labels.
Output the Discourse-native upload-marker shape `[upload|HASH]`
instead of echoing the source position back as `[attachment|N]`.
That matches what Discourse's import infrastructure recognizes —
the placeholder is what an importer feeds the upload-resolution
pass, not a thing the importer rewrites further.

To make that work the Tag needs the source post's attachment
table at render time, so it can look up SHA-1 by position.
Construct the renderer per post with that post's attachments
passed to the Tag constructor; the handler stays shared. The
extra notes call out the build-once-vs-per-post tradeoff:
post-independent state (handlers, escaper, postprocessor) lives
outside the loop, post-dependent state (the attachment table)
goes inside.

Emission now carries `{position, sha1, filename}` so the importer
gets both source-side identification and the resolved hash for
its own bookkeeping.

Diagram source updated to match (`[upload|HASH]` placeholder text,
emission shape includes the sha1). Re-export from Excalidraw to
refresh the rendered SVGs.
The hash in `[upload|HASH]` isn't a content hash — it's the stable
identifier the importer's converter framework derives from the
source filename/path so each placeholder maps unambiguously to one
source-side row.

Renames everywhere:
  - sha1 attribute → upload_id
  - emission key :attachments[:sha1] → :attachments[:upload_id]
  - sample value abc123def456 → u_screenshot_png_3a7c2 (suggestive
    of a path-derived ID, not a content hash)

The narrative now explicitly names this as a path-derived ID,
not a SHA-1, so future readers don't reach for `Digest::SHA1` and
get the wrong identifier. Diagram source updated to match; SVGs
need a re-export from Excalidraw to refresh.
Two corrections to the placeholder example:

  1. The AST node now carries `filename` directly. Following
     Markbridge's own AttachmentHandler, the parser uses
     RawContentCollector to capture the body text between
     `[attachment=0]` and `[/attachment]` at parse time and pins
     it on the node. AttachmentPlaceholder is now a `Node` (leaf)
     instead of `Element`, since there are no children to render.
     The Tag drops `interface.render_children` and reads
     `element.filename` directly.

  2. The emission key is `:uploads`, not `:attachments`. The
     downstream consumer is Discourse uploads, so the key matches
     what the importer is actually building. `result.emitted(:uploads)`
     now returns the placeholder records.

Diagram source updated to match: AST box says `(position: 0,
filename: ...)`, emissions box says `Conversion#emissions[:uploads]`.
SVGs need a re-export from Excalidraw to refresh.
"Worked example for **phpBB3**, which emits..." reads like a
journal heading. Switch to "Concrete example: phpBB3 emits...",
which signals "I just described the general shape — here's a
specific one" more directly and drops jargon-y bolding.
Add §5 to MIGRATION_API_PLAN.md capturing the design conclusion that
placeholder resolution belongs in custom handler subclasses at parse
time, not in renderer Tags at render time.

Implications, all written out:

  - The converter framework (a layer above Markbridge) owns the
    resolution-aware base handler classes, the placeholder AST
    classes, and the trivial render Tags. Markbridge keeps the parser
    primitives. Per-format converters (phpBB / vBulletin / SMF /
    IPB / XenForo) subclass the bases via template method, only
    overriding source-ref extraction.
  - Resolution is a *write* (store the upload, get the id), not a
    pure lookup. Parse-time resolution gives that side effect a
    natural once-per-attachment home; render-time would re-store on
    every render or need an in-Tag cache.
  - AST nodes carry resolved data; render Tags become trivial.
    `interface.emit` and `Conversion#emissions` (§2) are no longer
    needed and should be removed from PR #30 before merge.
  - Section ends with a concrete file-by-file change list for PR #30
    plus the docs-branch follow-up needed once §2 is removed.

§2 above is now marked obsoleted-by-§5. Working branch section
unchanged.
Replace the manual rv/ruby-install/cargo bootstrap with silo's `use:`
directive for Ruby (3.3/3.4/4.0/jruby, default 4.0) and Node, and bump
the base image to fedora/44. Ports now live under each daemon instead of
the top-level `ports:` list.

Add docs/pnpm-workspace.yaml approving the esbuild/sharp build scripts so
the new `CI=true pnpm install` runs non-interactively in setup/sync.
Re-export the placeholders diagrams from Excalidraw, updating the
viewBox/dimensions and embedded font data.
gschlager added 29 commits July 7, 2026 00:51
Pin pnpm@11.9.0 so the pnpm version is deterministic across local dev,
CI, and Dependabot. Matches the existing lockfile (v9.0); no lockfile
change needed.
Astro's sitemap integration emits the sitemap but no robots.txt to
advertise it. Add one (allow all + Sitemap: line).
Add a pull_request trigger (scoped to docs/**) so opening a PR validates
the Astro build on CI. Guard the deploy job with
github.event_name != 'pull_request' so Pages only publishes on push to
main / release. Scope concurrency per-ref so a PR build never blocks a
main deploy, and cancel superseded PR builds.
withastro/action's package-manager: pnpm input fights the
packageManager pin in docs/package.json — pnpm/action-setup errors with
'Multiple versions of pnpm specified'. Let the packageManager field be
the single source; the action still auto-detects pnpm from the lockfile.
pnpm 11.9.0 requires Node >= 22.13; the workflow was on Node 20, which
failed with ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite. Node 22 matches
local dev.
Bump withastro/action v3 → v6 (the v3 sub-actions target the now-deprecated
Node 20 and were being forced onto Node 24), checkout v6 → v7 to match
main.yml, and deploy-pages v4 → v5.
Each snippet runs in its own ruby subprocess for isolation. On JRuby and
TruffleRuby that's a JVM/Truffle cold start per block (dozens of them),
which dominated those CI jobs. The snippets test the documented public
API, which is engine-agnostic, so run them on CRuby only — the unit,
integration, and system specs still cover JRuby/TruffleRuby.
Discourse quotes render as [quote="alice, post:3, topic:7"], so they
need the importer to resolve the post/topic refs — they don't translate
straight across. Use headings as the example instead.
Astro's content-collection watcher is a separate chokidar instance that
doesn't inherit Vite's server.watch.usePolling, so on container/mounted
filesystems (where inotify is unreliable) Markdown edits weren't picked
up — the page kept serving stale content even after a hard refresh. Set
CHOKIDAR_USEPOLLING for the dev server, which every chokidar watcher in
the process reads.
I added CHOKIDAR_USEPOLLING on a wrong diagnosis. Tested it properly:
inotify works fine in the container (limits are huge and barely used),
and a fresh dev server picks up Markdown edits via inotify in ~1s with no
polling. The stale page came from a long-running Astro persistent
dev-server process whose watcher had desynced — 'astro dev stop' +
restart fixes it, no polling needed.
Same wrong assumption as the reverted CHOKIDAR_USEPOLLING: inotify works
fine in the Incus container. Verified after removal that both Markdown
(Astro content watcher) and custom.css (Vite watcher) hot-reload via
inotify. Keeps the Cache-Control: no-store header, which is about browser
caching of public/ assets in dev, not file watching.
'reads side data from conversion.ast' used a term not yet defined at the
top of the page and an implementation path. Relabel to 'importer reads
back from the AST' — names the actor and points at the AST::Document box.
Align the alt text. (overview SVGs need re-exporting from the source.)
The 'Four stages' list covered only the pipeline boxes, leaving the
'importer reads back from the AST' element in the diagram unexplained.
Spell out that read-back step in the closing paragraph and match the
diagram's wording so the box has a clear counterpart in the text.
The 'importer reads back from the AST' element was a placeholder detail
that confused more than it helped in a top-level pipeline diagram. Remove
it (box, label, and arrow) so the diagram is exactly the four stages, and
keep the read-back where it reads more naturally — in the prose, pointing
at the Placeholders page. (overview SVGs need re-exporting.)
'tokenizes the input' is only true for BBCode; HTML and TextFormatter
walk a Nokogiri DOM and MediaWiki is line-based. Say 'reads the input'
so the sentence holds for all four. (The unknown-tags-never-raise claim
was already accurate for every parser — verified.)
'Migrating to Discourse' sat second in the sidebar, which made the whole
site feel like it's only about migration. Move it below the general
sections (Format guides, Customization, Concepts) so migration reads as
one substantial use case, not the headline. Also drop a stray 'your
importer' from the overview intro.
The page leaned on a personified 'importer' as the subject of nearly
every sentence. It's already written in second person, so use 'you' /
'your own code' and drop 'importer' entirely (down from ~7 uses to 0).
The page still covers migration — that's its job — it just reads less
like jargon.
The restructure left no gem-level overview in the nav — Getting Started
was doing double duty as overview + quickstart. Add a short Introduction
(first nav item): what Markbridge is, the parse → AST → render pipeline
with the diagram, the four formats, when to use it, and a map of the
docs. Trim Getting Started back to a real quickstart — drop the
'What just happened' pipeline section (now in Introduction + Architecture)
and slim 'Where to next' to concrete next steps.
Adding the Introduction page left the migration overview repeating the
parse → AST → render pipeline (its own diagram + bullets). Replace the
'Four stages' section with a short 'how a migration uses the pipeline'
that links to the Introduction and focuses on the migration-specific
plug-in points (handlers in, Tags out, read-back after). Remove the now
unused overview diagram (svg + dark + excalidraw).
Lead with the hands-on quickstart; the Introduction (big picture + doc
map) sits right after for readers who want orientation.
Markbridge installs fine with 'gem install markbridge' — Bundler is only
for the 'bundle add' / Gemfile workflow, so it's optional, not a
requirement. The only requirement is Ruby 3.3+.
Format guides, Customization, and Concepts had no page to link to — prose
faked it (e.g. 'format guides' pointed at the BBCode guide). Add a short
Overview page for each as the group's first sidebar item, and point the
Getting Started and Introduction links at the real landings.
The nav, links, and section landing already use the bare format name;
the page titles were the last holdout. The '→ Markdown' was repetitive
(the whole gem converts to Markdown) and only added marginal SEO. Titles
now match the nav: BBCode / HTML / MediaWiki / TextFormatter.
The full Conversion write-up lived only on the migration overview, so
general pages (format guides, Getting Started) linked a general type to a
migration page. Add a 'Result objects' page under Concepts covering
Conversion and Parse, move the explanation there, and repoint the links.
The migration overview keeps a one-line bridge to it.
Every 'Supported tags' sub-table now uses Tags | Renders as | AST node
(the old Formatting-only 'Output'+'AST node' and the six 'Notes' tables
disagreed). Wrap the group tables in a .format-tags div and pin equal
column widths via CSS so the stacked tables line up. AST nodes verified
against the handler registry.
Same schema and aligned columns across BBCode, HTML, MediaWiki, and
TextFormatter: wrap each guide's tag tables in .format-tags for shared
column widths, merge the old Output/Notes/Attributes into a clean
'Renders as' (output only — no behavior notes), and add the AST-node
column where it was missing (MediaWiki nodes verified against the
parser).
The 'Renders as' cleanup dropped a few genuinely useful bits. Add them
back as short notes below the tables (not in the cells): BBCode's code
language, ordered-list, quote-attribution, and link-form variants; and
the attributes each TextFormatter element carries.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant