Skip to content

feat(mix_markdown): add Markdown renderer styled with Mix - #1056

Open
leoafarias wants to merge 9 commits into
mainfrom
claude/package-setup-simplify-3kwvvm
Open

leoafarias wants to merge 9 commits into
mainfrom
claude/package-setup-simplify-3kwvvm

Conversation

@leoafarias

@leoafarias leoafarias commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Related issue

Not linked to an open issue.

Description

Adds mix_markdown, a new package that renders Markdown through generated Mix Specs and Stylers, and fixes one mix_generator bug that the package surfaced.

MixMarkdown is a StyleWidget<MarkdownSpec>, so every text slot is a TextStyler and inherits the rest of Mix: tokens, onDark and other variants, widget modifiers, animation, and the generated call(). It parses a document once and reuses the parsed nodes across style, theme, and text-scale changes.

The supported subset is paragraphs, h1h6, nested inline formatting, and GitHub alerts (including nested alerts and shared link references). Links are styled but not activated, and inline images contribute their alt text. A document containing any other block goes to unsupportedBuilder, or shows a diagnostic, rather than being silently flattened.

The package started from an external handoff bundle that had never compiled. It was rebuilt against this repo's conventions rather than imported: the copied alert syntax was dropped in favour of the markdown package's own AlertBlockSyntax, the handwritten style classes and ProbeSpec aliases were replaced with generated Specs and Stylers, and the SuperDeck-specific hero handling, Python validation tooling, and review documents were left out.

Changes

New package packages/mix_markdown (version 0.0.1-beta.0)

  • MixMarkdown widget with syntax, wrapBlock, and unsupportedBuilder hooks.
  • Generated MarkdownSpec, MarkdownAlertSpec, and MarkdownAlertTypeSpec, with a slot per block, inline run, and alert type.
  • MarkdownSyntax value object for parser configuration; equal configurations do not trigger a reparse.
  • Pure-function unsupported-node scan under lib/src/parsing/, unit tested separately from the widgets.
  • 43 package tests plus an example smoke test, covering parse counts, brightness and scale changes, heading fallback, alert slots, inline span composition and text directives, semantics, RTL wrapping, and selection.
  • Example app with a theme toggle, README, CHANGELOG, LICENSE, and light/dark screenshots.
  • Publish-contract and public-API tests mirroring mix_chart.

mix_generator fix

  • firstInvisibleTypeName recursed into a type alias's expansion, so a visible typedef whose parameters name types the annotated library does not import was rejected. Generated code only ever writes the alias name, so the expansion does not need to be visible. Added a regression test.

Wiring and docs

  • melos.yaml: register the package and its example under flutter_projects.
  • .github/workflows/publish.yml: add the mix_markdown-v* tag pattern and publish job.
  • README.md, AGENTS.md, llms.txt, and the mix skill: list the package (the structure block in AGENTS.md now lists every package, not just the original four).

Review Checklist

  • Testing: Have you tested your changes, including unit tests and integration tests for affected code?
  • Breaking Changes: Does this change introduce breaking changes affecting existing code or users? — No. The package is new, and the generator fix only widens what the generator accepts.
  • Documentation Updates: Are all relevant documentation files (e.g. README, API docs) updated to reflect the changes in this PR?
  • Website Updates: Is the website containing the updates you make on documentation? — Not included. The package is experimental, and btwld/mix-docs lives outside this repo.

Additional Information (optional)

Verification on the current head (branch is merged up to date with main):

Check Result
melos run format:check SUCCESS
melos run analyze:dart No issues, every package
mix_markdown tests 43 pass
mix_markdown/example test 1 pass
mix_generator tests 379 pass
dart pub publish --dry-run Passes, only the expected melos-override hints
pana 140/160 locally; the missing points are sandbox artifacts (no cwebp, no network for URL checks, shared lint file excluded from the archive by .pubignore, exactly as mix_chart does)

DCM could not run in the environment used to prepare this, since it has no licence there. CI skips it too, so its rules were applied by hand (member ordering, newline-before-return, named booleans).

Where a reviewer should start:

  1. lib/src/specs/markdown_spec.dart for the public style contract. The main open design question is whether the slot set is the right one, particularly that inline slots contribute only their resolved TextStyle (inline runs are spans, not widgets, so modifiers and animation cannot apply to them).
  2. lib/src/widgets/markdown_document.dart for the parse lifecycle. The state parses in initState and didUpdateWidget only, never in build, which is what keeps style changes free. Parse counts are asserted in the widget tests.
  3. lib/src/parsing/unsupported_nodes.dart for the subset boundary. Worth checking the policy is the one the project wants: a paragraph holding only an image reports standalone-image rather than rendering alt text, and unsupported blocks anywhere, including inside an alert, reject the whole document.
  4. packages/mix_generator/lib/src/core/helpers/library_scope.dart for the one-line behavioural change outside the new package.

Decisions worth a second opinion:

  • The version is 0.0.1-beta.0 with publish metadata, matching how mix_chart entered the repo. Say if this should ship with publish_to: none until the API settles.
  • Dependency floors are mix: ^2.2.0-beta.5 and mix_annotations: ^2.2.0-beta.1, following mix_chart in not tracking every release bump.
  • Missing heading slots fall back to paragraph, and blockSpacing defaults to zero. No theme-derived typography defaults, matching Mix's explicit-style approach.
  • Active links, lists, tables, and fenced code are deliberately out of scope for this first cut.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QnNfDRXutaME1C2FAGBYKv

claude and others added 8 commits September 9, 2026 02:10
…den types

The widget-call visibility check recursed into a type alias's underlying
function type, so a visible typedef whose parameters name types the spec
library does not import was rejected. Generated code only ever writes the
alias name, so the aliased declaration's parts do not need to be visible.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnNfDRXutaME1C2FAGBYKv
Adds an experimental package that parses a Markdown document once and
renders paragraphs, headings, nested inline formatting, and GitHub alerts
through generated Specs and Stylers.

- MixMarkdown is a StyleWidget over a generated MarkdownSpec, so every
  text slot is a TextStyler with tokens, variants, modifiers, and animation.
- Alerts use the markdown package's own GitHub alert syntax and resolve
  per-type MarkdownAlertTypeSpec slots.
- Style, theme, and text-scale updates reuse the parsed document; only new
  data or syntax parses again.
- Documents with unsupported blocks go to an unsupportedBuilder or show a
  diagnostic instead of being flattened.
- Registers the package and its example with melos and the mix skill.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnNfDRXutaME1C2FAGBYKv
The markdown parser keeps a soft line break as a newline inside a text
node, so wrapped source lines rendered as hard breaks. Outside inline code
those newlines now become spaces; hard breaks still arrive as `br`
elements and raw `<br>` tags still become line breaks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnNfDRXutaME1C2FAGBYKv
- Version 0.0.1-beta.0 with a pub.dev description, topics, and light and
  dark screenshots of the example.
- Type the `style` parameter as `MarkdownStyler` so dot shorthand works at
  call sites, matching mix_chart.
- Add the publish-contract and public-API tests used by mix_chart.
- Add an example README and list mix_chart and mix_markdown in the root
  README package table.
- Add the mix_markdown tag pattern and job to the publish workflow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnNfDRXutaME1C2FAGBYKv
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnNfDRXutaME1C2FAGBYKv
- Move the unsupported-node scan to `parsing/unsupported_nodes.dart`; it is
  a pure AST check with no widget dependency. Its test moves with it.
- Move `MarkdownBlocks` to its own widget file so the alert widget no
  longer imports the document state to reach it.
- Keep the example's document style private.
- List every package in the AGENTS.md structure block and add the
  mix_markdown README to llms.txt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnNfDRXutaME1C2FAGBYKv
The cache class and the state both tracked the parsed nodes and compared
sources, so the state now owns the parse directly. Parser-level tests move
to the syntax test; parse counts stay covered by the widget tests, which
now also assert that a new syntax changes the rendered output.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnNfDRXutaME1C2FAGBYKv
The Markdown tag vocabulary was spelled out as bare strings in four places
that had to be kept in sync by hand: the eligibility sets, the heading
style switch, the inline style switch, and a `startsWith('h')` test for
heading semantics that was also true for `hr`.

MarkdownBlockTag and MarkdownInlineTag now hold that vocabulary once. Both
switches are exhaustive over the enum, so adding a tag fails compilation at
every site that has to handle it. The public API is unchanged: the
unsupported-node scan still reports raw tag names.

Also drops a test that asserted the barrel exports the same six paths it
lists, and replaces two `isA` assertions that were true by construction
with checks on the resolved spec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnNfDRXutaME1C2FAGBYKv

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants