Skip to content

web: MDX without React - #21949

Open
GirlBossRush wants to merge 4 commits into
mainfrom
mdx-sans-react
Open

web: MDX without React#21949
GirlBossRush wants to merge 4 commits into
mainfrom
mdx-sans-react

Conversation

@GirlBossRush

Copy link
Copy Markdown
Contributor

Replaces the React-based <ak-mdx> (and the @mdx-js/mdx runtime evaluator behind it) with a build-time Markdown→HTML pipeline plus a pair of light-DOM custom-element wrappers. The rewrite removes the last runtime use of new AsyncFunction(...) from the bundle, so the page CSP no longer needs 'unsafe-eval' for <ak-mdx> to function.

Build pipeline (web/bundler/mdx-plugin/node.js):

  • Compiles each .md / .mdx to HTML via unified + remark + rehype.
  • Resolves relative ./foo.md hrefs against the docs site at build time and adds target="_blank" + rel="noopener noreferrer".
  • Wraps every <a> in <ak-md-a> and every <pre><code class="language-mermaid"> in <ak-md-mermaid> (wrap-don't-replace, so the original semantic HTML survives for graceful degradation).
  • Translates Docusaurus-style :::name Title admonitions into the spec form :::name[Title] before parsing, then promotes the title paragraph to <strong> inside <ak-alert>.
  • Adds caution and tip to the admonition vocabulary, mapping them to the closest PFAlert level.
  • Bakes frontmatter title: into a <h1 part="title"> and wraps the whole document in <div class="pf-c-content" part="content">.

Runtime (web/src/elements/ak-mdx/):

  • <ak-mdx> is a thin Lit element. URL mode fetches the JSON envelope and stamps the pre-rendered HTML through CompiledMarkdownTrustPolicy (a passthrough Trusted Types policy for our own build output). Content mode runs the same unified pipeline in-browser for admin-supplied prose, sanitised through BrandedHTMLPolicy.
  • <ak-md-a> (light-DOM Lit element via :host { display: contents }) attaches a click interceptor on the slotted <a> so fragment links scroll within the host shadow tree instead of overwriting location.hash and yanking the hash-routed SPA off its current page.
  • <ak-md-mermaid> delegates to the existing <ak-diagram> element, using the slotted <pre><code> as the source of mermaid markup.

Mermaid theming (web/src/elements/Interface.ts, web/src/common/mermaid.ts):

  • applyMermaidTheme is now invoked once at startup with the resolved current theme so the first SVG render of a page loaded in dark mode comes out in the right colours, instead of waiting for the user's first explicit theme toggle to fix it.
  • <ak-diagram> listens for AKMermaidRefreshEvent on window and calls requestUpdate() on each fire, picking up the latest global mermaid config without per-instance theme tracking.

Dependencies:

  • Drop @mdx-js/mdx, react, react-dom, rehype-mermaid, remark-mdx-frontmatter.
  • Promote unified, remark-parse, remark-rehype, mdast-util-to-string from transitive to direct.
  • Convert the remaining react imports (HTMLAttributes, ImgHTMLAttributes, LabelHTMLAttributes) to import type so the bundler erases them entirely.

Tests:

  • web/test/browser/ak-mdx.test.ts — Playwright e2e, exercises frontmatter, heading slugs, admonitions, link wrapping (external, relative, fragment), mermaid render, and theme-toggle re-rendering against the OAuth2 provider docs.

@netlify

netlify Bot commented Apr 28, 2026

Copy link
Copy Markdown

Deploy Preview for authentik-storybook ready!

Name Link
🔨 Latest commit c025fdd
🔍 Latest deploy log https://app.netlify.com/projects/authentik-storybook/deploys/6a2a446ea26aa2000808c64b
😎 Deploy Preview https://deploy-preview-21949--authentik-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Apr 28, 2026

Copy link
Copy Markdown

Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit e40c1bf
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/6a05d8960f84930008bdd9d8
😎 Deploy Preview https://deploy-preview-21949--authentik-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Apr 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.27%. Comparing base (269a897) to head (c025fdd).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21949      +/-   ##
==========================================
+ Coverage   93.25%   93.27%   +0.02%     
==========================================
  Files        1032     1032              
  Lines       60164    60164              
  Branches      400      400              
==========================================
+ Hits        56104    56119      +15     
+ Misses       4060     4045      -15     
Flag Coverage Δ
conformance 36.53% <ø> (+<0.01%) ⬆️
e2e 41.79% <ø> (+<0.01%) ⬆️
integration 32.97% <ø> (+<0.01%) ⬆️
rust 0.00% <ø> (ø)
unit 92.20% <ø> (+<0.01%) ⬆️
unit-migrate 92.24% <ø> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@github-actions

github-actions Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

authentik PR Installation instructions

Instructions for docker-compose

Add the following block to your .env file:

AUTHENTIK_IMAGE=ghcr.io/goauthentik/dev-server
AUTHENTIK_TAG=gh-9a9c87208a5e6a488dbcc5cbaf99eafb46e75b60
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s

Afterwards, run the upgrade commands from the latest release notes.

Instructions for Kubernetes

Add the following block to your values.yml file:

authentik:
    outposts:
        container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
global:
    image:
        repository: ghcr.io/goauthentik/dev-server
        tag: gh-9a9c87208a5e6a488dbcc5cbaf99eafb46e75b60

Afterwards, run the upgrade commands from the latest release notes.

@GirlBossRush GirlBossRush self-assigned this Apr 29, 2026
@GirlBossRush GirlBossRush added area:frontend Features or issues related to the browser, TypeScript, Node.js, etc area:docs Features or issues related to Docusaurus labels Apr 29, 2026
@GirlBossRush
GirlBossRush marked this pull request as ready for review April 29, 2026 02:32
@GirlBossRush
GirlBossRush requested a review from a team as a code owner April 29, 2026 02:32
@GirlBossRush
GirlBossRush force-pushed the mdx-sans-react branch 3 times, most recently from 4ce183f to 7f7802a Compare April 30, 2026 13:02
@GirlBossRush
GirlBossRush force-pushed the mdx-sans-react branch 2 times, most recently from 05fa351 to dc1a879 Compare June 2, 2026 16:49
@kensternberg-authentik

Copy link
Copy Markdown
Contributor

Suggestion 1: Make the first change (Extract Mermaid) into its own PR.

Comment thread web/src/admin/flows/FlowDiagram.ts Outdated

protected refresh = (): void => {
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesDiagramRetrieve({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aki(FlowsApi) ? Now that it's merged?

Comment thread web/src/elements/mermaid/utils.ts
@GirlBossRush

Copy link
Copy Markdown
Contributor Author

Split out into #22980, with both of the FlowDiagram suggestions applied over there.

@kensternberg-authentik kensternberg-authentik left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I see here is:

  1. the <ak-mdx> and <ak-md-a> elements are consumers of markdown to produce HTML.

  2. The bundler is also a consumer of markdown to produce HTML.

  3. The transform presets by remark are duplicated in two places, and could be unified and published as a separate package in the same way you've got packages for eslint or prettier.

  4. The bundler plugin is probably also publishable as a separate package. It takes the content as arguments and produces the corresponding output.

Comment thread web/src/common/purify.ts
// plain text.
const normalized = normalizeAdmonitionLabels(source);

const file = await unified()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this different from the process done in the Bundler, and is there a risk that we'll make changes to one without also changing the other?

The left hoof wants to say "Centralize this process into a common function," and another that says," and on the other hoof I wonder if there's enough difference between the two to just put a comment here reminding people that if you change this, change the Bundler as well.

‎web/bundler/mdx-plugin/compile.js

@GirlBossRush
GirlBossRush force-pushed the mdx-sans-react branch 3 times, most recently from 66c83df to 9a9c872 Compare June 11, 2026 03:31
GirlBossRush and others added 3 commits June 11, 2026 06:47
Replace `@mdx-js/mdx`'s `evaluate`/`run` (which depends on `'unsafe-eval'`
in the page CSP) with a pure `unified`/remark/rehype pipeline. URL-mode
content now arrives from the build-time `mdx-plugin` as pre-rendered HTML
and is stamped through a Trusted Types passthrough policy
(`CompiledMarkdownTrustPolicy`) so admin-side custom elements like
`<ak-alert>` and `<ak-md-a>` survive. Content-mode (admin-supplied
markdown) is compiled in-browser via `compileRuntimeMarkdown` and routed
through the existing `BrandedHTMLPolicy` (DOMPurify).

`<ak-md-a>` replaces the React `MDXAnchor`/`MDXWrapper` pair: a tiny Lit
custom element with `display: contents` that intercepts in-doc fragment
clicks for shadow-root scrolling. The remark plugins gain `caution` and
`tip` admonition types and promote `:::name[Title]` directive labels to
`<strong>`. `one-dark.css` learns `:host` so syntax-highlighted code
blocks inherit the palette inside `<ak-mdx>`'s shadow tree.

Removes `@mdx-js/mdx`, `react`, `react-dom`, `rehype-mermaid`, and
`remark-mdx-frontmatter`. Adds `unified`, `remark-parse`, `remark-rehype`,
and `mdast-util-to-string`.

Co-Authored-By: Agent <279763771+playpen-agent@users.noreply.github.com>
Compile each `.md` / `.mdx` file through a build-time `unified` pipeline
(remark-parse, GFM, frontmatter, directives, custom admonition/headings/
lists transforms; rehype-rehype, anchor wrapping, syntax highlighting,
mermaid extraction) and emit a JSON envelope of
`{ content: HTML, frontmatter, publicPath, publicDirectory }`. The
on-load result still ships via the `file` loader so the runtime side
keeps its existing fetch path. The compiled HTML uses `<ak-md-a>` and
`<ak-alert>` custom elements so `<ak-mdx>` can stamp it directly into
shadow DOM with no client-side JavaScript evaluation.

Plugin is split across four sibling files (`node.js`, `compile.js`,
`remark.js`, `rehype.js`) so each concern lives in its own module. The
title/wrapper HTML is built as a hast tree and serialized through
`hast-util-to-html` rather than string-concatenated, dropping the
hand-rolled `escapeHTML` helper and unifying escape semantics with the
rest of the document.

Co-Authored-By: Agent <279763771+playpen-agent@users.noreply.github.com>
The OAuth2 provider docs page exercises the full pipeline in one place:
frontmatter title, multiple H2 slugs, `:::caution`/`:::info` admonitions
(with and without titles), relative-doc links, external links, and an
embedded mermaid sequence diagram. Tests boot the admin UI, provision a
fresh OAuth2 provider, navigate to its view page, and assert against the
compiled DOM inside `<ak-mdx>`'s shadow root.

Co-Authored-By: Agent <279763771+playpen-agent@users.noreply.github.com>
Address PR review feedback on the URL-mode trust boundary. `<ak-mdx>`'s
`replacers` hook runs over pre-rendered build-time HTML before it is
stamped into the DOM, and consumers (e.g. `ProxyProviderViewPage`) splice
admin-controlled values such as `provider.externalHost` into it. The old
React pipeline ran replacers on raw markdown that was then compiled, so
those values were HTML-escaped on serialization; the new URL mode passed
the post-replacer HTML straight through, dropping that guarantee.

Replace the passthrough `CompiledMarkdownTrustPolicy` with
`CompiledMarkdownSanitizePolicy`: a DOMPurify policy that whitelists the
custom elements (`<ak-alert>`, `<ak-md-a>`, `<ak-diagram>`) and the
`part`/`level` attributes our pipeline emits, and strips anything else a
replacer could inject.

Also add a reciprocal drift note to the runtime `markdown.ts` pointing at
`bundler/mdx-plugin/`, mirroring the existing note on the bundler side.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@BeryJu BeryJu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a visual standpoint in the UI everything looks great, I'll leave the code review to @kensternberg-authentik

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

Labels

area:docs Features or issues related to Docusaurus area:frontend Features or issues related to the browser, TypeScript, Node.js, etc

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

4 participants